6
6
7
7
class ServerTest < ActiveSupport ::TestCase
8
8
setup do
9
- @server = RubyLsp ::Rails ::Server . new
9
+ @mutex = T . let ( Mutex . new , Mutex )
10
+
11
+ stdin , stdout , stderr , wait_thread = Bundler . with_original_env do
12
+ path = "#{ __dir__ } /../../lib/ruby_lsp/ruby_lsp_rails/server.rb"
13
+ Open3 . popen3 (
14
+ "bundle" ,
15
+ "exec" ,
16
+ "rails" ,
17
+ "runner" ,
18
+ path ,
19
+ "start" ,
20
+ )
21
+ end
22
+
23
+ @stdin = T . let ( stdin , IO )
24
+ @stdout = T . let ( stdout , IO )
25
+ @stderr = T . let ( stderr , IO )
26
+ @wait_thread = T . let ( wait_thread , Process ::Waiter )
27
+ end
28
+
29
+ teardown do
30
+ if @wait_thread . alive?
31
+ $stderr. puts ( "Ruby LSP Rails is force killing the server" )
32
+ sleep ( 0.5 ) # give the server a bit of time if we already issued a shutdown notification
33
+ force_kill
34
+ end
10
35
end
11
36
12
37
test "returns nil if model doesn't exist" do
13
- response = @server . execute ( "model" , { name : "Foo" } )
38
+ response = make_request ( "model" , { name : "Foo" } )
39
+ # binding.break
14
40
assert_nil ( response . fetch ( :result ) )
15
41
end
16
42
17
43
test "returns nil if class is not a model" do
18
- response = @server . execute ( "model" , { name : "Time" } )
44
+ response = make_request ( "model" , { name : "Time" } )
19
45
assert_nil ( response . fetch ( :result ) )
20
46
end
21
47
22
48
test "returns nil if class is an abstract model" do
23
- response = @server . execute ( "model" , { name : "ApplicationRecord" } )
49
+ response = make_request ( "model" , { name : "ApplicationRecord" } )
24
50
assert_nil ( response . fetch ( :result ) )
25
51
end
26
52
27
53
test "returns nil if constant is not a class" do
28
- response = @server . execute ( "model" , { name : "RUBY_VERSION" } )
54
+ response = make_request ( "model" , { name : "RUBY_VERSION" } )
29
55
assert_nil ( response . fetch ( :result ) )
30
56
end
31
57
@@ -38,21 +64,21 @@ def <(other)
38
64
end
39
65
end
40
66
41
- response = @server . execute ( "model" , { name : "TestClassWithOverwrittenLessThan" } )
67
+ response = make_request ( "model" , { name : "TestClassWithOverwrittenLessThan" } )
42
68
assert_nil ( response . fetch ( :result ) )
43
69
end
44
70
45
71
test "handles older Rails version which don't have `schema_dump_path`" do
46
72
ActiveRecord ::Tasks ::DatabaseTasks . send ( :alias_method , :old_schema_dump_path , :schema_dump_path )
47
73
ActiveRecord ::Tasks ::DatabaseTasks . undef_method ( :schema_dump_path )
48
- response = @server . execute ( "model" , { name : "User" } )
74
+ response = make_request ( "model" , { name : "User" } )
49
75
assert_nil ( response . fetch ( :result ) [ :schema_file ] )
50
76
ensure
51
77
ActiveRecord ::Tasks ::DatabaseTasks . send ( :alias_method , :schema_dump_path , :old_schema_dump_path )
52
78
end
53
79
54
80
test "resolve association returns the location of the target class of a has_many association" do
55
- response = @server . execute (
81
+ response = make_request (
56
82
"association_target_location" ,
57
83
{ model_name : "Organization" , association_name : :memberships } ,
58
84
)
@@ -61,7 +87,7 @@ def <(other)
61
87
end
62
88
63
89
test "resolve association returns the location of the target class of a belongs_to association" do
64
- response = @server . execute (
90
+ response = make_request (
65
91
"association_target_location" ,
66
92
{ model_name : "Membership" , association_name : :organization } ,
67
93
)
@@ -70,7 +96,7 @@ def <(other)
70
96
end
71
97
72
98
test "resolve association returns the location of the target class of a has_one association" do
73
- response = @server . execute (
99
+ response = make_request (
74
100
"association_target_location" ,
75
101
{ model_name : "User" , association_name : :profile } ,
76
102
)
@@ -79,7 +105,7 @@ def <(other)
79
105
end
80
106
81
107
test "resolve association returns the location of the target class of a has_and_belongs_to_many association" do
82
- response = @server . execute (
108
+ response = make_request (
83
109
"association_target_location" ,
84
110
{ model_name : "Profile" , association_name : :labels } ,
85
111
)
@@ -88,23 +114,23 @@ def <(other)
88
114
end
89
115
90
116
test "resolve association handles invalid model name" do
91
- response = @server . execute (
117
+ response = make_request (
92
118
"association_target_location" ,
93
119
{ model_name : "NotHere" , association_name : :labels } ,
94
120
)
95
121
assert_nil ( response . fetch ( :result ) )
96
122
end
97
123
98
124
test "resolve association handles invalid association name" do
99
- response = @server . execute (
125
+ response = make_request (
100
126
"association_target_location" ,
101
127
{ model_name : "Membership" , association_name : :labels } ,
102
128
)
103
129
assert_nil ( response . fetch ( :result ) )
104
130
end
105
131
106
132
test "resolve association handles class_name option" do
107
- response = @server . execute (
133
+ response = make_request (
108
134
"association_target_location" ,
109
135
{ model_name : "User" , association_name : :location } ,
110
136
)
@@ -113,18 +139,18 @@ def <(other)
113
139
end
114
140
115
141
test "route location returns the location for a valid route" do
116
- response = @server . execute ( "route_location" , { name : "user_path" } )
142
+ response = make_request ( "route_location" , { name : "user_path" } )
117
143
location = response [ :result ] [ :location ]
118
144
assert_match %r{test/dummy/config/routes.rb:4$} , location
119
145
end
120
146
121
147
test "route location returns nil for invalid routes" do
122
- response = @server . execute ( "route_location" , { name : "invalid_path" } )
148
+ response = make_request ( "route_location" , { name : "invalid_path" } )
123
149
assert_nil response [ :result ]
124
150
end
125
151
126
152
test "route info" do
127
- response = @server . execute ( "route_info" , { controller : "UsersController" , action : "index" } )
153
+ response = make_request ( "route_info" , { controller : "UsersController" , action : "index" } )
128
154
129
155
result = response [ :result ]
130
156
@@ -152,4 +178,52 @@ def resolve_route_info(requirements)
152
178
assert_empty ( stdout )
153
179
assert_equal ( "Hello\n " , stderr )
154
180
end
181
+
182
+ private
183
+
184
+ def make_request ( request , params = nil )
185
+ send_message ( request , params )
186
+ read_response
187
+ end
188
+
189
+ def send_message ( request , params = nil )
190
+ message = { method : request }
191
+ message [ :params ] = params if params
192
+ json = message . to_json
193
+
194
+ @mutex . synchronize do
195
+ @stdin . write ( "Content-Length: #{ json . length } \r \n \r \n " , json )
196
+ end
197
+ # rescue Errno::EPIPE
198
+ # The server connection died
199
+ end
200
+
201
+ def read_response
202
+ raw_response = @mutex . synchronize do
203
+ headers = @stdout . gets ( "\r \n \r \n " )
204
+ raise "IncompleteMessageError" unless headers
205
+
206
+ content_length = headers [ /Content-Length: (\d +)/i , 1 ] . to_i
207
+ raise "EmptyMessageError" if content_length . zero?
208
+
209
+ @stdout . read ( content_length )
210
+ end
211
+
212
+ response = JSON . parse ( T . must ( raw_response ) , symbolize_names : true )
213
+
214
+ if response [ :error ]
215
+ $stderr. puts ( "Ruby LSP Rails error: " + response [ :error ] )
216
+ return
217
+ end
218
+
219
+ response . fetch ( :result )
220
+ # rescue Errno::EPIPE
221
+ # The server connection died
222
+ # nil
223
+ end
224
+
225
+ def force_kill
226
+ # Windows does not support the `TERM` signal, so we're forced to use `KILL` here
227
+ Process . kill ( T . must ( Signal . list [ "KILL" ] ) , @wait_thread . pid )
228
+ end
155
229
end
0 commit comments