Skip to content

Commit f061e2a

Browse files
committed
(GH-97) Create a crash file when the language server abends
Previously when the language server crashed, no debug information would be created which would make it difficult to diagnose issues in the field. This commit adds the facility to create a crash file in %TMP%\puppet_language_server_crash.txt. This commit also adds tests for this facility.
1 parent 2b12075 commit f061e2a

File tree

3 files changed

+257
-0
lines changed

3 files changed

+257
-0
lines changed

server/lib/puppet-languageserver/message_router.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,57 @@ def self.document(uri)
1919
return nil if @documents[uri].nil?
2020
@documents[uri].clone
2121
end
22+
23+
def self.document_uris
24+
@documents.keys.dup
25+
end
26+
end
27+
28+
module CrashDump
29+
def self.default_crash_file
30+
File.join(Dir.tmpdir(),'puppet_language_server_crash.txt')
31+
end
32+
33+
def self.write_crash_file(err, filename = nil, additional = {})
34+
# Create the crash text
35+
36+
puppet_version = Puppet.version rescue 'Unknown'
37+
facter_version = Facter.version rescue 'Unknown'
38+
languageserver_version = PuppetLanguageServer.version rescue 'Unknown'
39+
40+
crashtext = <<-TEXT
41+
Puppet Language Server Crash File
42+
-=--=--=--=--=--=--=--=--=--=--=-
43+
#{DateTime.now.strftime("%a %b %e %Y %H:%M:%S %Z")}
44+
Puppet Version #{puppet_version}
45+
Facter Version #{facter_version}
46+
Ruby Version #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}
47+
Language Server Version #{languageserver_version}
48+
49+
Error: #{err.to_s}
50+
51+
Backtrace
52+
---------
53+
#{err.backtrace.join("\n") }
54+
55+
TEXT
56+
# Append the documents in the cache
57+
PuppetLanguageServer::DocumentStore.document_uris.each do |uri|
58+
crashtext += "Document - #{uri}\n---\n#{PuppetLanguageServer::DocumentStore.document(uri)}\n\n"
59+
end
60+
# Append additional objects from the crash
61+
additional.each do |k,v|
62+
crashtext += "#{k}\n---\n#{v.to_s}\n\n"
63+
end
64+
65+
crash_file = filename.nil? ? default_crash_file : filename
66+
File.open(crash_file, 'wb') { |file| file.write(crashtext) }
67+
rescue
68+
# Swallow all errors. Errors in the error handler should not
69+
# terminate the application
70+
end
71+
72+
nil
2273
end
2374

2475
class MessageRouter < JSONRPCHandler
@@ -134,6 +185,12 @@ def receive_request(request)
134185
else
135186
PuppetLanguageServer.log_message(:error, "Unknown RPC method #{request.rpc_method}")
136187
end
188+
rescue => err
189+
PuppetLanguageServer::CrashDump.write_crash_file(err, nil, {
190+
'request' => request.rpc_method,
191+
'params' => request.params,
192+
})
193+
raise
137194
end
138195

139196
def receive_notification(method, params)
@@ -170,6 +227,12 @@ def receive_notification(method, params)
170227
else
171228
PuppetLanguageServer.log_message(:error, "Unknown RPC notification #{method}")
172229
end
230+
rescue => err
231+
PuppetLanguageServer::CrashDump.write_crash_file(err, nil, {
232+
'notification' => method,
233+
'params' => params,
234+
})
235+
raise
173236
end
174237
end
175238
end
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
require 'spec_helper'
2+
3+
describe 'message_router' do
4+
let(:subject_options) { nil }
5+
let(:subject) { PuppetLanguageServer::MessageRouter.new(subject_options) }
6+
7+
describe '#receive_request' do
8+
let(:documents) {{
9+
'file1.rb' => "file1_line1\nfile1_line2\nfile1_line3\nfile1_line4\n"
10+
}}
11+
let(:request_connection) { MockJSONRPCHandler.new() }
12+
let(:request_rpc_method) { nil }
13+
let(:request_params) { { 'crashparam1' => 'crashvalue1'} }
14+
let(:request_id) { 0 }
15+
let(:request) { PuppetLanguageServer::JSONRPCHandler::Request.new(
16+
request_connection,request_id,request_rpc_method,request_params) }
17+
18+
before(:each) do
19+
allow(PuppetLanguageServer).to receive(:log_message)
20+
21+
# Populate the document cache
22+
PuppetLanguageServer::DocumentStore.clear
23+
documents.each { |uri, content| PuppetLanguageServer::DocumentStore.set_document(uri, content) }
24+
end
25+
26+
context 'given a request that raises an error' do
27+
let(:request_rpc_method) { 'puppet/getVersion' }
28+
before(:each) do
29+
@default_crash_file = PuppetLanguageServer::CrashDump.default_crash_file
30+
File.delete(@default_crash_file) if File.exists?(@default_crash_file)
31+
32+
expect(Puppet).to receive(:version).at_least(:once).and_raise('MockError')
33+
end
34+
35+
it 'should create a default crash dump file' do
36+
begin
37+
subject.receive_request(request)
38+
rescue
39+
end
40+
41+
expect(File.exists?(@default_crash_file)).to be(true)
42+
end
43+
44+
context 'the content of the crash dump file' do
45+
before(:each) do
46+
# Force a crash dump to occur
47+
begin
48+
subject.receive_request(request)
49+
rescue
50+
end
51+
@crash_content = File.open(@default_crash_file, 'rb') { |file| file.read }
52+
end
53+
54+
it 'should contain the error text' do
55+
expect(@crash_content).to match(/Error: MockError/)
56+
end
57+
58+
it 'should contain a backtrace' do
59+
expect(@crash_content).to match(/Backtrace/)
60+
expect(@crash_content).to match(/message_router.rb/)
61+
end
62+
63+
it 'should contain the request method' do
64+
expect(@crash_content).to match(/request/)
65+
expect(@crash_content).to match(request_rpc_method)
66+
end
67+
68+
it 'should contain the current document cache' do
69+
documents.each do |uri,content|
70+
expect(@crash_content).to match(uri)
71+
expect(@crash_content).to match(content)
72+
end
73+
end
74+
75+
it 'should contain the request parameters' do
76+
expect(@crash_content).to match(/params/)
77+
request_params.each do |k,v|
78+
expect(@crash_content).to match(k)
79+
expect(@crash_content).to match(v)
80+
end
81+
end
82+
end
83+
end
84+
end
85+
86+
describe '#receive_notification' do
87+
let(:documents) {{
88+
'file1.rb' => "file1_line1\nfile1_line2\nfile1_line3\nfile1_line4\n"
89+
}}
90+
let(:notification_method) { nil }
91+
let(:notification_params) { { 'crashparam2' => 'crashvalue2'} }
92+
93+
before(:each) do
94+
allow(PuppetLanguageServer).to receive(:log_message)
95+
96+
# Populate the document cache
97+
PuppetLanguageServer::DocumentStore.clear
98+
documents.each { |uri, content| PuppetLanguageServer::DocumentStore.set_document(uri, content) }
99+
end
100+
101+
context 'given a request that raises an error' do
102+
let(:notification_method) { 'exit' }
103+
before(:each) do
104+
@default_crash_file = PuppetLanguageServer::CrashDump.default_crash_file
105+
File.delete(@default_crash_file) if File.exists?(@default_crash_file)
106+
107+
expect(subject).to receive(:close_connection).and_raise('MockError')
108+
end
109+
110+
it 'should create a default crash dump file' do
111+
begin
112+
subject.receive_notification(notification_method, notification_params)
113+
rescue
114+
end
115+
116+
expect(File.exists?(@default_crash_file)).to be(true)
117+
end
118+
119+
context 'the content of the crash dump file' do
120+
before(:each) do
121+
# Force a crash dump to occur
122+
begin
123+
subject.receive_notification(notification_method, notification_params)
124+
rescue
125+
end
126+
@crash_content = File.open(@default_crash_file, 'rb') { |file| file.read }
127+
end
128+
129+
it 'should contain the error text' do
130+
expect(@crash_content).to match(/Error: MockError/)
131+
end
132+
133+
it 'should contain a backtrace' do
134+
expect(@crash_content).to match(/Backtrace/)
135+
expect(@crash_content).to match(/message_router.rb/)
136+
end
137+
138+
it 'should contain the notification name' do
139+
expect(@crash_content).to match(/notification/)
140+
expect(@crash_content).to match(notification_method)
141+
end
142+
143+
it 'should contain the current document cache' do
144+
documents.each do |uri,content|
145+
expect(@crash_content).to match(uri)
146+
expect(@crash_content).to match(content)
147+
end
148+
end
149+
150+
it 'should contain the request parameters' do
151+
expect(@crash_content).to match(/params/)
152+
notification_params.each do |k,v|
153+
expect(@crash_content).to match(k)
154+
expect(@crash_content).to match(v)
155+
end
156+
end
157+
end
158+
end
159+
end
160+
end

server/spec/unit/puppet-languageserver/message_router_spec.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@
2222
allow(PuppetLanguageServer).to receive(:log_message)
2323
end
2424

25+
context 'given a request that raises an error' do
26+
let(:request_rpc_method) { 'puppet/getVersion' }
27+
before(:each) do
28+
expect(Puppet).to receive(:version).and_raise('MockError')
29+
allow(PuppetLanguageServer::CrashDump).to receive(:write_crash_file)
30+
end
31+
32+
it 'should raise an error' do
33+
expect{ subject.receive_request(request) }.to raise_error(/MockError/)
34+
end
35+
36+
it 'should call PuppetLanguageServer::CrashDump.write_crash_file' do
37+
expect(PuppetLanguageServer::CrashDump).to receive(:write_crash_file)
38+
expect{ subject.receive_request(request) }.to raise_error(/MockError/)
39+
end
40+
end
41+
2542
# initialize - https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialize
2643
context 'given an initialize request' do
2744
let(:request_rpc_method) { 'initialize' }
@@ -386,6 +403,23 @@
386403
let(:notification_method) { nil }
387404
let(:notification_params) { {} }
388405

406+
context 'given a notification that raises an error' do
407+
let(:notification_method) { 'exit' }
408+
before(:each) do
409+
expect(subject).to receive(:close_connection).and_raise('MockError')
410+
allow(PuppetLanguageServer::CrashDump).to receive(:write_crash_file)
411+
end
412+
413+
it 'should raise an error' do
414+
expect{ subject.receive_notification(notification_method, notification_params) }.to raise_error(/MockError/)
415+
end
416+
417+
it 'should call PuppetLanguageServer::CrashDump.write_crash_file' do
418+
expect(PuppetLanguageServer::CrashDump).to receive(:write_crash_file)
419+
expect{ subject.receive_notification(notification_method, notification_params) }.to raise_error(/MockError/)
420+
end
421+
end
422+
389423
# initialized - https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialized
390424
context 'given an initialized notification' do
391425
let(:notification_method) { 'initialized' }

0 commit comments

Comments
 (0)