rtext 0.8.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +120 -89
  3. data/Project.yaml +15 -0
  4. data/RText_Protocol +47 -4
  5. data/lib/rtext/context_builder.rb +49 -8
  6. data/lib/rtext/default_completer.rb +212 -163
  7. data/lib/rtext/default_service_provider.rb +3 -3
  8. data/lib/rtext/frontend/connector.rb +130 -56
  9. data/lib/rtext/instantiator.rb +11 -3
  10. data/lib/rtext/language.rb +5 -5
  11. data/lib/rtext/serializer.rb +3 -3
  12. data/lib/rtext/service.rb +281 -253
  13. data/lib/rtext/tokenizer.rb +2 -2
  14. metadata +33 -33
  15. data/Rakefile +0 -46
  16. data/test/completer_test.rb +0 -606
  17. data/test/context_builder_test.rb +0 -948
  18. data/test/frontend/context_test.rb +0 -301
  19. data/test/instantiator_test.rb +0 -1704
  20. data/test/integration/backend.out +0 -13
  21. data/test/integration/crash_on_request_editor.rb +0 -12
  22. data/test/integration/ecore_editor.rb +0 -50
  23. data/test/integration/frontend.log +0 -38203
  24. data/test/integration/model/invalid_encoding.invenc +0 -2
  25. data/test/integration/model/test.crash_on_request +0 -18
  26. data/test/integration/model/test.crashing_backend +0 -18
  27. data/test/integration/model/test.dont_open_socket +0 -0
  28. data/test/integration/model/test.invalid_cmd_line +0 -0
  29. data/test/integration/model/test.not_in_rtext +0 -0
  30. data/test/integration/model/test_large_with_errors.ect3 +0 -43523
  31. data/test/integration/model/test_metamodel.ect +0 -24
  32. data/test/integration/model/test_metamodel2.ect +0 -5
  33. data/test/integration/model/test_metamodel3.ect4 +0 -7
  34. data/test/integration/model/test_metamodel_error.ect2 +0 -3
  35. data/test/integration/model/test_metamodel_ok.ect2 +0 -18
  36. data/test/integration/test.rb +0 -966
  37. data/test/link_detector_test.rb +0 -287
  38. data/test/message_helper_test.rb +0 -118
  39. data/test/rtext_test.rb +0 -11
  40. data/test/serializer_test.rb +0 -1004
  41. data/test/tokenizer_test.rb +0 -173
@@ -21,18 +21,25 @@ def initialize(config, options={})
21
21
  @outfile_provider = options[:outfile_provider]
22
22
  @keep_outfile = options[:keep_outfile]
23
23
  @connection_timeout = options[:connection_timeout] || 10
24
+ @process_id = nil
24
25
  end
25
26
 
26
27
  def execute_command(obj, options={})
27
- timeout = options[:timeout] || 5
28
+ timeout = options[:timeout] || 10
28
29
  @busy = false if @busy_start_time && (Time.now > @busy_start_time + timeout)
29
30
  if @busy
30
31
  do_work
31
- :backend_busy
32
- elsif connected?
32
+ return :backend_busy
33
+ end
34
+ unless connected?
35
+ connect unless connecting?
36
+ do_work
37
+ end
38
+ if connected?
33
39
  obj["invocation_id"] = @invocation_id
34
40
  obj["type"] = "request"
35
41
  @socket.send(serialize_message(obj), 0)
42
+ @logger.debug('Sent request') if @logger
36
43
  result = nil
37
44
  @busy = true
38
45
  @busy_start_time = Time.now
@@ -66,9 +73,8 @@ def execute_command(obj, options={})
66
73
  result
67
74
  end
68
75
  else
69
- connect unless connecting?
70
- do_work
71
- :connecting
76
+ @logger.debug('connecting')
77
+ :connecting
72
78
  end
73
79
  end
74
80
 
@@ -83,27 +89,72 @@ def stop
83
89
  end
84
90
  if connected?
85
91
  execute_command({"type" => "request", "command" => "stop"})
86
- while do_work
92
+ while do_work
87
93
  sleep(0.1)
88
94
  end
89
95
  end
96
+ ensure_process_cleanup(@process_id, @keep_outfile ? nil : @out_file, 10)
97
+ @process_id = nil
90
98
  end
91
99
 
92
100
  private
93
101
 
102
+ def wait_for_process_to_exit(process_id, timeout)
103
+ with_timeout timeout do
104
+ begin
105
+ waitpid(process_id, Process::WNOHANG)
106
+ process_id = nil
107
+ true
108
+ rescue Errno::ECHILD => _
109
+ false
110
+ end
111
+ end
112
+ end
113
+
114
+ def ensure_process_cleanup(process_id, out_file, timeout)
115
+ Thread.new do
116
+ begin
117
+ unless process_id.nil?
118
+ process_id = nil if wait_for_process_to_exit(process_id, timeout)
119
+ end
120
+ ensure
121
+ unless process_id.nil?
122
+ begin
123
+ Process.kill('QUIT', process_id)
124
+ rescue Errno::ESRCH => _
125
+ end
126
+ end
127
+ File.unlink(out_file) if !out_file.nil? && File.exist?(out_file)
128
+ end
129
+ end
130
+ end
131
+
132
+ def with_timeout(timeout, sleep_time = 0.1, &block)
133
+ started = Time.now
134
+ while true do
135
+ return true if block.call
136
+ if Time.now > started + timeout
137
+ return false
138
+ end
139
+ sleep(sleep_time)
140
+ end
141
+ end
142
+
143
+
94
144
  def connected?
95
- @state == :connected && backend_running?
145
+ !@process_id.nil? && @state == :read_from_socket && backend_running?
96
146
  end
97
147
 
98
148
  def connecting?
99
- @state == :connecting
149
+ !@process_id.nil? && (@state == :wait_for_file || @state == :wait_for_port)
100
150
  end
101
151
 
102
152
  def backend_running?
103
153
  if @process_id
104
154
  begin
105
- return true unless waitpid(@process_id, Process::WNOHANG)
106
- rescue Errno::ECHILD
155
+ waitpid(@process_id, Process::WNOHANG)
156
+ return true
157
+ rescue Errno::ECHILD => _
107
158
  end
108
159
  end
109
160
  false
@@ -121,66 +172,87 @@ def tempfile_name
121
172
  end
122
173
 
123
174
  def connect
124
- @state = :connecting
175
+ return if connected?
125
176
  @connect_start_time = Time.now
126
177
 
127
178
  @logger.info @config.command if @logger
128
179
 
129
180
  if @outfile_provider
130
- @out_file = @outfile_provider.call
181
+ @out_file = File.expand_path(@outfile_provider.call)
131
182
  else
132
- @out_file = tempfile_name
183
+ @out_file = File.expand_path(tempfile_name)
133
184
  end
134
- File.unlink(@out_file) if File.exist?(@out_file)
135
185
 
136
- Dir.chdir(File.dirname(@config.file)) do
137
- @process_id = spawn(@config.command.strip + " > #{@out_file} 2>&1")
186
+ if @process_id.nil?
187
+ File.unlink(@out_file) if File.exist?(@out_file)
188
+ Dir.chdir(File.dirname(@config.file)) do
189
+ @logger.debug(@config.command.strip + " > #{@out_file} 2>&1") if @logger
190
+ @process_id = spawn(@config.command.strip + " > #{@out_file} 2>&1")
191
+ @state = :wait_for_file
192
+ end
138
193
  end
139
- @work_state = :wait_for_file
140
194
  end
141
195
 
142
196
  def do_work
143
- case @work_state
144
- when :wait_for_file
145
- if File.exist?(@out_file)
146
- @work_state = :wait_for_port
147
- end
148
- if Time.now > @connect_start_time + @connection_timeout
149
- cleanup
150
- @connection_listener.call(:timeout) if @connection_listener
151
- @work_state = :done
152
- @state = :off
153
- @logger.warn "process didn't startup (connection timeout)" if @logger
197
+ unless @process_id
198
+ @state = :off
199
+ @logger.debug('No process id') if @logger
200
+ return false
201
+ end
202
+ if @state == :wait_for_port && !File.exist?(@out_file)
203
+ @state = :wait_for_file
204
+ end
205
+ if @state == :wait_for_file && File.exist?(@out_file)
206
+ @state = :wait_for_port
207
+ end
208
+ if @state == :wait_for_file
209
+ while true
210
+ if Time.now > @connect_start_time + @connection_timeout
211
+ cleanup
212
+ @connection_listener.call(:timeout) if @connection_listener
213
+ @state = :off
214
+ @logger.warn "process didn't startup (connection timeout)" if @logger
215
+ return false
216
+ end
217
+ sleep(0.1)
218
+ if File.exist?(@out_file)
219
+ @state = :wait_for_port
220
+ break
221
+ end
154
222
  end
155
- true
156
- when :wait_for_port
157
- output = File.read(@out_file)
158
- if output =~ /^RText service, listening on port (\d+)/
159
- port = $1.to_i
160
- @logger.info "connecting to #{port}" if @logger
161
- begin
162
- @socket = TCPSocket.new("127.0.0.1", port)
163
- @socket.setsockopt(:SOCKET, :RCVBUF, 1000000)
164
- rescue Errno::ECONNREFUSED
223
+ end
224
+ if @state == :wait_for_port
225
+ while true
226
+ break unless File.exist?(@out_file)
227
+ output = File.read(@out_file)
228
+ if output =~ /^RText service, listening on port (\d+)/
229
+ port = $1.to_i
230
+ @logger.info "connecting to #{port}" if @logger
231
+ begin
232
+ @socket = TCPSocket.new("127.0.0.1", port)
233
+ @socket.setsockopt(:SOCKET, :RCVBUF, 1000000)
234
+ rescue Errno::ECONNREFUSED
235
+ cleanup
236
+ @connection_listener.call(:timeout) if @connection_listener
237
+ @state = :off
238
+ @logger.warn "could not connect socket (connection timeout)" if @logger
239
+ return false
240
+ end
241
+ @state = :read_from_socket
242
+ @connection_listener.call(:connected) if @connection_listener
243
+ break
244
+ end
245
+ if Time.now > @connect_start_time + @connection_timeout
165
246
  cleanup
166
247
  @connection_listener.call(:timeout) if @connection_listener
167
- @work_state = :done
168
248
  @state = :off
169
249
  @logger.warn "could not connect socket (connection timeout)" if @logger
250
+ return false
170
251
  end
171
- @state = :connected
172
- @work_state = :read_from_socket
173
- @connection_listener.call(:connected) if @connection_listener
174
- end
175
- if Time.now > @connect_start_time + @connection_timeout
176
- cleanup
177
- @connection_listener.call(:timeout) if @connection_listener
178
- @work_state = :done
179
- @state = :off
180
- @logger.warn "could not connect socket (connection timeout)" if @logger
252
+ sleep(0.1)
181
253
  end
182
- true
183
- when :read_from_socket
254
+ end
255
+ if @state == :read_from_socket
184
256
  repeat = true
185
257
  socket_closed = false
186
258
  response_data = ""
@@ -195,6 +267,7 @@ def do_work
195
267
  @logger.info "server socket closed (end of file)" if @logger
196
268
  end
197
269
  if data
270
+ @logger.debug('Got data') if @logger
198
271
  repeat = true
199
272
  response_data.concat(data)
200
273
  while obj = extract_message(response_data)
@@ -208,13 +281,13 @@ def do_work
208
281
  end
209
282
  elsif !backend_running? || socket_closed
210
283
  cleanup
211
- @work_state = :done
284
+ @state = :off
285
+ @logger.debug("backend_running?: #{backend_running?}, socket_closed: #{socket_closed}") if @logger
212
286
  return false
213
287
  end
214
288
  end
215
- true
216
289
  end
217
-
290
+ true
218
291
  end
219
292
 
220
293
  def cleanup
@@ -224,7 +297,8 @@ def cleanup
224
297
  break unless backend_running?
225
298
  sleep(0.1)
226
299
  end
227
- File.unlink(@out_file) unless @keep_outfile
300
+ ensure_process_cleanup(@process_id, @keep_outfile ? nil : @out_file, 10)
301
+ @process_id = nil
228
302
  end
229
303
 
230
304
  end
@@ -247,7 +247,15 @@ class Instantiator
247
247
  element.setOrAddGeneric(feature.name, proxy)
248
248
  else
249
249
  begin
250
- element.setOrAddGeneric(feature.name, v.value)
250
+ v_value = v.value
251
+ feature_instance_class = feature.eType.instanceClass
252
+ if feature_instance_class == String && (v_value.is_a?(Float) || v_value.is_a?(Fixnum))
253
+ element.setOrAddGeneric(feature.name, v_value.to_s)
254
+ elsif feature_instance_class == Float && v_value.is_a?(Fixnum)
255
+ element.setOrAddGeneric(feature.name, v_value.to_f)
256
+ else
257
+ element.setOrAddGeneric(feature.name, v_value)
258
+ end
251
259
  rescue StandardError
252
260
  # backward compatibility for RGen versions not supporting BigDecimal
253
261
  if v.value.is_a?(BigDecimal)
@@ -302,9 +310,9 @@ class Instantiator
302
310
  elsif feature.eType.is_a?(RGen::ECore::EEnum)
303
311
  [:identifier, :string]
304
312
  else
305
- expected = { String => [:string, :identifier],
313
+ expected = { String => [:string, :identifier, :integer, :float],
306
314
  Integer => [:integer],
307
- Float => [:float],
315
+ Float => [:float, :integer],
308
316
  RGen::MetamodelBuilder::DataTypes::Boolean => [:boolean],
309
317
  Object => [:string, :identifier, :integer, :float, :boolean]
310
318
  }[feature.eType.instanceClass]
@@ -260,7 +260,7 @@ class Language
260
260
  end
261
261
 
262
262
  def concrete_types(clazz)
263
- ([clazz] + clazz.eAllSubTypes).select{|c| !c.abstract}
263
+ ([clazz] + clazz.eAllSubTypes).select{|c| c.concrete}
264
264
  end
265
265
 
266
266
  def containments_by_target_type(clazz, type)
@@ -299,7 +299,7 @@ class Language
299
299
  @command_by_class = {}
300
300
  @has_command = {}
301
301
  root_epackage.eAllClasses.each do |c|
302
- next if c.abstract
302
+ next unless c.concrete
303
303
  cmd = command_name_provider.call(c)
304
304
  @command_by_class[c.instanceClass] = cmd
305
305
  @has_command[cmd] = true
@@ -307,7 +307,7 @@ class Language
307
307
  @class_by_command[clazz] ||= {}
308
308
  containments(c).collect{|r|
309
309
  [r.eType] + r.eType.eAllSubTypes}.flatten.uniq.each do |t|
310
- next if t.abstract
310
+ next unless t.concrete
311
311
  cmw = command_name_provider.call(t)
312
312
  raise "ambiguous command name #{cmw}" if @class_by_command[clazz][cmw]
313
313
  @class_by_command[clazz][cmw] = t.instanceClass
@@ -315,7 +315,7 @@ class Language
315
315
  end
316
316
  @class_by_command[nil] = {}
317
317
  @root_classes.each do |c|
318
- next if c.abstract
318
+ next unless c.concrete
319
319
  cmw = command_name_provider.call(c)
320
320
  raise "ambiguous command name #{cmw}" if @class_by_command[nil][cmw]
321
321
  @class_by_command[nil][cmw] = c.instanceClass
@@ -323,7 +323,7 @@ class Language
323
323
  end
324
324
 
325
325
  def default_root_classes(root_package)
326
- root_epackage.eAllClasses.select{|c| !c.abstract &&
326
+ root_epackage.eAllClasses.select{|c| c.concrete &&
327
327
  !c.eAllReferences.any?{|r| r.eOpposite && r.eOpposite.containment}}
328
328
  end
329
329
 
@@ -152,7 +152,7 @@ class Serializer
152
152
  result << v.to_s
153
153
  end
154
154
  elsif feature.eType.instanceClass == String
155
- if @lang.unquoted?(feature) && v.to_s =~ /^[a-zA-Z_]\w*$/ && v.to_s != "true" && v.to_s != "false"
155
+ if @lang.unquoted?(feature) && v.to_s =~ /^[a-zA-Z_]\w*$/m && v.to_s != "true" && v.to_s != "false"
156
156
  result << v.to_s
157
157
  else
158
158
  result << "\"#{v.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
@@ -166,7 +166,7 @@ class Serializer
166
166
  # formatting not available for BigDecimals
167
167
  else
168
168
  if arg_format
169
- result << sprintf(arg_format, v)
169
+ result << sprintf(arg_format, v)
170
170
  else
171
171
  result << v.to_s
172
172
  end
@@ -179,7 +179,7 @@ class Serializer
179
179
  result << v.to_s
180
180
  end
181
181
  elsif feature.eType.instanceClass == Object
182
- if v.to_s =~ /^\d+(\.\d+)?$|^\w+$|^true$|^false$/
182
+ if v.to_s =~ /^-?\d+(\.\d+)?$|^\w+$|^true$|^false$/
183
183
  result << v.to_s
184
184
  else
185
185
  result << "\"#{v.to_s.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").