turbot-runner 0.0.24 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +8 -8
  2. data/lib/turbot_runner/base_handler.rb +13 -0
  3. data/lib/{prerun.rb → turbot_runner/prerun.rb} +0 -0
  4. data/lib/turbot_runner/processor.rb +55 -0
  5. data/lib/turbot_runner/runner.rb +150 -0
  6. data/lib/turbot_runner/script_runner.rb +90 -0
  7. data/lib/turbot_runner/version.rb +1 -1
  8. data/lib/turbot_runner.rb +5 -335
  9. data/spec/bots/bot-that-crashes-in-scraper/manifest.json +8 -0
  10. data/spec/bots/bot-that-crashes-in-scraper/scraper.rb +11 -0
  11. data/spec/bots/bot-that-crashes-in-transformer/manifest.json +20 -0
  12. data/spec/bots/bot-that-crashes-in-transformer/scraper.rb +10 -0
  13. data/spec/bots/bot-that-crashes-in-transformer/transformer1.rb +15 -0
  14. data/spec/bots/bot-that-crashes-in-transformer/transformer2.rb +17 -0
  15. data/spec/bots/bot-with-pause/manifest.json +8 -0
  16. data/spec/bots/bot-with-pause/scraper.rb +16 -0
  17. data/spec/bots/bot-with-transformer/manifest.json +15 -0
  18. data/spec/bots/bot-with-transformer/scraper.rb +10 -0
  19. data/spec/bots/bot-with-transformer/transformer.rb +15 -0
  20. data/spec/bots/bot-with-transformers/manifest.json +20 -0
  21. data/spec/bots/bot-with-transformers/scraper.rb +10 -0
  22. data/spec/bots/bot-with-transformers/transformer1.rb +15 -0
  23. data/spec/bots/bot-with-transformers/transformer2.rb +15 -0
  24. data/spec/bots/invalid-json-bot/manifest.json +8 -0
  25. data/spec/bots/invalid-json-bot/scraper.rb +11 -0
  26. data/spec/bots/invalid-record-bot/manifest.json +8 -0
  27. data/spec/bots/invalid-record-bot/scraper.rb +11 -0
  28. data/spec/bots/logging-bot/manifest.json +8 -0
  29. data/spec/bots/logging-bot/scraper.rb +14 -0
  30. data/spec/bots/python-bot/manifest.json +8 -0
  31. data/spec/bots/python-bot/scraper.py +11 -0
  32. data/spec/bots/ruby-bot/manifest.json +8 -0
  33. data/spec/bots/ruby-bot/scraper.rb +10 -0
  34. data/spec/bots/slow-bot/manifest.json +8 -0
  35. data/spec/bots/slow-bot/scraper.rb +11 -0
  36. data/spec/lib/processor.rb +48 -0
  37. data/spec/lib/runner_spec.rb +244 -0
  38. data/spec/manual_spec.rb +55 -0
  39. data/spec/outputs/full-scraper.out +10 -0
  40. data/spec/outputs/full-transformer.out +10 -0
  41. data/spec/outputs/truncated-scraper.out +5 -0
  42. metadata +40 -19
  43. data/spec/dummy-bot-python/manifest.json +0 -15
  44. data/spec/dummy-bot-python/scraper.py +0 -11
  45. data/spec/dummy-bot-python/transformer.py +0 -15
  46. data/spec/dummy-bot-ruby/manifest.json +0 -15
  47. data/spec/dummy-bot-ruby/scraper.rb +0 -8
  48. data/spec/dummy-bot-ruby/transformer.rb +0 -12
  49. data/spec/dummy-broken-bot-ruby/manifest.json +0 -8
  50. data/spec/dummy-broken-bot-ruby/scraper.rb +0 -6
  51. data/spec/dummy-broken-bot-ruby/transformer.rb +0 -12
  52. data/spec/dummy-broken-bot-ruby-2/manifest.json +0 -15
  53. data/spec/dummy-broken-bot-ruby-2/scraper.rb +0 -4
  54. data/spec/dummy-broken-bot-ruby-2/transformer.rb +0 -11
  55. data/spec/dummy-broken-bot-ruby-3/manifest.json +0 -15
  56. data/spec/dummy-broken-bot-ruby-3/scraper.rb +0 -5
  57. data/spec/dummy-broken-bot-ruby-3/transformer.rb +0 -5
  58. data/spec/turbot_runner_spec.rb +0 -117
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YTNmMmJmZDNkNDllMTk0MjFmYTkxOTMxZWI3NzNiYjdlYzQ2NzgzMg==
4
+ YWYzYWQ4OTMwNGNiMDMxMjJlYzRlMDMyMGNjMmM0ZGNlZjA5MzdiNQ==
5
5
  data.tar.gz: !binary |-
6
- MThhNmM4ZGM3OTU2NDAyZGRkM2EzZTE2ZTA4ZDQzMjFmNTE3ODFmNw==
6
+ ZTJjZGMxNDlkN2EyMmVjZDJjZWNmZWQ0ZWE5NTJjN2EyYjgxYjRkNQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Yjc0NjdiZTc2NWE1NzAwMGIzMTBhNTIxNGE5YmQ4ZjFmMWMwZjcyMWJlYWFk
10
- ZDM0YjcyNDRiYTE2MjMzOTQ4MDA4YzkwNjAzOGUyOGNjNmI3Y2U3YzEwODdk
11
- YmQ1ZjYzYjdmNGY5ODIwZjZhMjQ5ODQ5OThlNWU4MDg1M2I5ODM=
9
+ Yjg3YThjYzk5NTBhMjFjN2Q1MzMxNmY2N2ZhNDhmNDM3MmUxN2ZiYzgwZTM0
10
+ ZTM0ZWJmNDA2NDljZDk4YTM0NDlmMjc3ODk3ZjY3NDk3MjZhZTM1ZjJlNzFi
11
+ Mzg5ODdkODIwNjU3YjVmNmI3OWQ2YjBjZjZlMmIzNTk1MTZmZDQ=
12
12
  data.tar.gz: !binary |-
13
- YjNkMDAyMTBkYjY4YTg5ZWM5NDdlOGE2OWE5OGNkMzRkMDI1MjA4OWJjMDQ3
14
- NTJiODM4MzIzNDQ2ZDg2ZTNlZDJkYTNmMzk4ODExODlhOTdjNTU0NDc5MmI1
15
- NWIyMTY2MTVjOGI1OWYxNmNjMzJlOTdlZDU1Yzk0NzQxODc1ZmQ=
13
+ NDQ2ZjY4YmE1NTI5NjRiM2U3ZTVlYjhlOTY1NDc2NGJjMGM1ODdhZDAzZjE5
14
+ ZWFhZWFmOTIwYTUyNjhjZDNiOWI3ZDliNWU1ODdlZDViZGYzNjZiZGYzMTg3
15
+ Y2NkZjE3ODVhYWI4YWQ2NGQzZTI4YTc0NTI3NzA2OGJhMjhiZDM=
@@ -0,0 +1,13 @@
1
+ module TurbotRunner
2
+ class BaseHandler
3
+ def handle_valid_record(record, data_type)
4
+ true
5
+ end
6
+
7
+ def handle_invalid_record(record, data_type, line)
8
+ end
9
+
10
+ def handle_invalid_json(line)
11
+ end
12
+ end
13
+ end
File without changes
@@ -0,0 +1,55 @@
1
+ require 'json'
2
+ require 'json-schema'
3
+
4
+ module TurbotRunner
5
+ class Processor
6
+ def initialize(runner, data_type, record_handler)
7
+ @runner = runner
8
+ @data_type = data_type
9
+ @record_handler = record_handler
10
+ end
11
+
12
+ def process(line)
13
+ begin
14
+ record = JSON.parse(line)
15
+ errors = validate(record)
16
+
17
+ if errors.empty?
18
+ rc = @record_handler.handle_valid_record(record, @data_type)
19
+ @runner.interrupt unless rc
20
+ else
21
+ @record_handler.handle_invalid_record(record, @data_type, errors)
22
+ @runner.interrupt_and_mark_as_failed
23
+ end
24
+ rescue JSON::ParserError
25
+ @record_handler.handle_invalid_json(line)
26
+ @runner.interrupt_and_mark_as_failed
27
+ end
28
+ end
29
+
30
+ def interrupt
31
+ @runner.interrupt
32
+ end
33
+
34
+ def validate(record)
35
+ errors = JSON::Validator.fully_validate(schema, record, :errors_as_objects => true)
36
+ messages = errors.map do |error|
37
+ case error[:message]
38
+ when /The property '#\/' did not contain a required property of '(\w+)'/
39
+ "Missing required attribute: #{Regexp.last_match(1)}"
40
+ else
41
+ error[:message]
42
+ end
43
+ end
44
+ end
45
+
46
+ def schema
47
+ @schema ||= get_schema
48
+ end
49
+
50
+ def get_schema
51
+ hyphenated_name = @data_type.to_s.gsub("_", "-").gsub(" ", "-")
52
+ File.expand_path("../../../schema/schemas/#{hyphenated_name}-schema.json", __FILE__)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,150 @@
1
+ require 'json'
2
+ require 'fileutils'
3
+
4
+ module TurbotRunner
5
+ class Runner
6
+ attr_reader :directory
7
+
8
+ def initialize(directory, options={})
9
+ @directory = directory
10
+ @config = load_config(directory)
11
+ @record_handler = options[:record_handler]
12
+ @log_to_file = options[:log_to_file]
13
+ @timeout = options[:timeout]
14
+ end
15
+
16
+ def run
17
+ FileUtils.rm_rf(output_directory)
18
+ FileUtils.mkdir_p(output_directory)
19
+
20
+ return false if not run_scraper
21
+
22
+ transformers.each do |transformer|
23
+ return false if not run_transformer(transformer)
24
+ end
25
+
26
+ true
27
+ end
28
+
29
+ def process_output
30
+ return false if not process_scraper_output
31
+
32
+ transformers.each do |transformer|
33
+ return false if not process_transformer_output(transformer)
34
+ end
35
+
36
+ true
37
+ end
38
+
39
+ private
40
+ def load_config(directory)
41
+ manifest_path = File.join(directory, 'manifest.json')
42
+ raise "Could not find #{manifest_path}" unless File.exist?(manifest_path)
43
+
44
+ begin
45
+ json = open(manifest_path) {|f| f.read}
46
+ JSON.parse(json, :symbolize_names => true)
47
+ rescue JSON::ParserError
48
+ # TODO provide better error message
49
+ raise "Could not parse #{manifest_path} as JSON"
50
+ end
51
+ end
52
+
53
+ def run_scraper
54
+ run_script(scraper_script, scraper_data_type)
55
+ end
56
+
57
+ def run_transformer(transformer)
58
+ run_script(
59
+ transformer[:file],
60
+ transformer[:data_type],
61
+ input_file=scraper_output_file
62
+ )
63
+ end
64
+
65
+ def run_script(script, data_type, input_file=nil)
66
+ command = build_command(script, input_file)
67
+
68
+ runner = ScriptRunner.new(
69
+ command,
70
+ output_file(script),
71
+ data_type,
72
+ :record_handler => @record_handler,
73
+ :timeout => @timeout
74
+ )
75
+
76
+ runner.run
77
+ end
78
+
79
+ def process_scraper_output
80
+ process_script_output(scraper_script, scraper_data_type)
81
+ end
82
+
83
+ def process_transformer_output(transformer)
84
+ process_script_output(transformer[:file], transformer[:data_type])
85
+ end
86
+
87
+ def process_script_output(script, data_type)
88
+ processor = Processor.new(nil, data_type, @record_handler)
89
+
90
+ File.open(output_file(script)) do |f|
91
+ f.each_line do |line|
92
+ processor.process(line)
93
+ end
94
+ end
95
+ end
96
+
97
+ def build_command(script, input_file=nil)
98
+ raise "Could not run #{script} with #{language}" unless script_extension == File.extname(script)
99
+ path_to_script = File.join(@directory, script)
100
+ command = "#{language} #{additional_args} #{path_to_script} >#{output_file(script)}"
101
+ command << " 2>#{output_file(script, '.err')}" if @log_to_file
102
+ command << " <#{input_file}" unless input_file.nil?
103
+
104
+ command
105
+ end
106
+
107
+ def output_file(script, extension='.out')
108
+ basename = File.basename(script, script_extension)
109
+ File.join(output_directory, basename) + extension
110
+ end
111
+
112
+ def script_extension
113
+ {
114
+ 'ruby' => '.rb',
115
+ 'python' => '.py',
116
+ }[language]
117
+ end
118
+
119
+ def additional_args
120
+ {
121
+ 'ruby' => "-r#{File.expand_path('../prerun.rb', __FILE__)}",
122
+ 'python' => '-u',
123
+ }[language]
124
+ end
125
+
126
+ def scraper_script
127
+ "scraper#{script_extension}"
128
+ end
129
+
130
+ def transformers
131
+ @config[:transformers] || []
132
+ end
133
+
134
+ def scraper_output_file
135
+ File.join(output_directory, 'scraper.out')
136
+ end
137
+
138
+ def language
139
+ @config[:language].downcase
140
+ end
141
+
142
+ def scraper_data_type
143
+ @config[:data_type]
144
+ end
145
+
146
+ def output_directory
147
+ File.join(@directory, 'output')
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,90 @@
1
+ require 'timeout'
2
+
3
+ # This is a useful blog post:
4
+ # http://blog.robseaman.com/2008/12/12/sending-ctrl-c-to-a-subprocess-with-ruby
5
+
6
+ # Ensure that SIGINT is ignored by the process running this.
7
+ trap('INT') {}
8
+
9
+ module TurbotRunner
10
+ class ScriptRunner
11
+ def initialize(command, output_file, data_type, options={})
12
+ @command = command
13
+ @output_file = output_file
14
+
15
+ record_handler = options[:record_handler] || BaseHandler.new # A BaseHandler does nothing
16
+ @processor = Processor.new(self, data_type, record_handler)
17
+
18
+ @timeout = options[:timeout] || 3600
19
+ end
20
+
21
+ def run
22
+ @interrupted = false
23
+ @failed = false
24
+
25
+ # Start a thread that spawns a subprocess that runs the script and
26
+ # redirects the script's output to a file at a known location.
27
+ script_thread = Thread.new { run_command(@command) }
28
+
29
+ # Wait for the output file to be created, so that we can start to read
30
+ # from it.
31
+ begin
32
+ f = File.open(@output_file)
33
+ rescue Errno::ENOENT
34
+ sleep 0.1
35
+ retry
36
+ end
37
+
38
+ # Read from output file line by line until either we reach the end of the
39
+ # file and the script has exited, or @interrupted becomes true.
40
+ until @interrupted do
41
+ begin
42
+ line = f.readline
43
+ @processor.process(line)
44
+ rescue EOFError
45
+ break unless script_thread.alive?
46
+ sleep 0.1
47
+ end
48
+ end
49
+
50
+ # script_thread may still be alive if we exited the loop above becuase
51
+ # @interrupted became true, and so we must kill it.
52
+ kill_running_processes if script_thread.alive?
53
+
54
+ @failed ? false : script_thread.join.value
55
+ ensure
56
+ f.close if f
57
+ end
58
+
59
+ def interrupt
60
+ @interrupted = true
61
+ end
62
+
63
+ def interrupt_and_mark_as_failed
64
+ @interrupted = true
65
+ @failed = true
66
+ end
67
+
68
+ private
69
+ def run_command(command)
70
+ begin
71
+ Timeout::timeout(@timeout) do
72
+ system(command)
73
+
74
+ # A nil exitstatus indicates that the script was interrupted. A
75
+ # termsig of 2 indicates that the script was interrupted by a SIGINT.
76
+ $?.exitstatus == 0 || ($?.exitstatus.nil? && $?.termsig == 2)
77
+ end
78
+ rescue Timeout::Error
79
+ kill_running_processes
80
+ false
81
+ end
82
+ end
83
+
84
+ def kill_running_processes
85
+ # Send SIGINT to each process in the current proceess group, having
86
+ # already ensured that the current process itself ignores the signal.
87
+ Process.kill('INT', 0)
88
+ end
89
+ end
90
+ end
@@ -1,3 +1,3 @@
1
1
  module TurbotRunner
2
- VERSION = '0.0.24'
2
+ VERSION = '0.1.0'
3
3
  end
data/lib/turbot_runner.rb CHANGED
@@ -1,335 +1,5 @@
1
- #require 'active_support/core_ext/hash/slice'
2
- #require 'active_support/core_ext/object/to_query'
3
- require 'json'
4
- require 'json-schema'
5
- require 'open3'
6
- require 'set'
7
- require 'timeout'
8
- require 'io/wait'
9
-
10
- module TurbotRunner
11
- class ScriptError < StandardError; end
12
-
13
- class BaseRunner
14
-
15
- attr_reader :wait_thread
16
- attr_reader :error
17
-
18
- def initialize(bot_directory)
19
- @bot_directory = bot_directory
20
-
21
- manifest_path = File.join(bot_directory, 'manifest.json')
22
- raise "Could not find #{manifest_path}" unless File.exist?(manifest_path)
23
-
24
- begin
25
- @config = JSON.parse(open(manifest_path) {|f| f.read})
26
- rescue JSON::ParserError
27
- # TODO provide better error message
28
- raise "Could not parse #{manifest_path} as JSON"
29
- end
30
-
31
- @status = :initialized
32
- @interrupted = false
33
- @schemas = {}
34
- end
35
-
36
- def run(opts={})
37
- @status = :running
38
-
39
- command = "#{interpreter_for(scraper_file)} #{scraper_file}"
40
- data_type = @config['data_type']
41
-
42
- scraper_runner = CommandRunner.new(command)
43
-
44
- transformers.each do |config|
45
- file = File.join(@bot_directory, config['file'])
46
- command = "#{interpreter_for(file)} #{file}"
47
- transformer_runner = CommandRunner.new(command)
48
- config['runner'] = transformer_runner
49
- end
50
-
51
- begin
52
- until @interrupted do
53
- line = scraper_runner.get_next_line
54
-
55
- if line.nil?
56
- if scraper_runner.finished?
57
- if scraper_runner.success?
58
- break
59
- else
60
- scraper_runner.raise_if_failed!
61
- end
62
- else
63
- next
64
- end
65
- end
66
-
67
- begin
68
- record = JSON.parse(line)
69
- rescue JSON::ParserError
70
- handle_non_json_output(line)
71
- next
72
- end
73
-
74
- errors = validate(record, data_type)
75
-
76
- if errors.empty?
77
- handle_valid_record(record, data_type)
78
-
79
- transformers.each do |transformer|
80
- data_type1 = transformer['data_type']
81
-
82
- runner = transformer['runner']
83
- runner.raise_if_failed!
84
-
85
- runner.send_line(line)
86
- line1 = runner.get_next_line
87
-
88
- if line1.nil?
89
- if runner.finished?
90
- if runner.success?
91
- break
92
- else
93
- runner.raise_if_failed!
94
- end
95
- else
96
- next
97
- end
98
- end
99
-
100
- # A transformer can output an empty line if it doesn't make
101
- # sense to transform a record.
102
- if line1.strip.empty?
103
- puts
104
- next
105
- end
106
-
107
- begin
108
- record1 = JSON.parse(line1)
109
- rescue JSON::ParserError
110
- handle_non_json_output(line1)
111
- next
112
- end
113
-
114
- errors = validate(record1, data_type1)
115
-
116
- if errors.empty?
117
- handle_valid_record(record1, data_type1)
118
- else
119
- handle_invalid_record(record1, data_type1, errors)
120
- end
121
- end
122
- else
123
- handle_invalid_record(record, data_type, errors)
124
- end
125
- end
126
- if @interrupted
127
- @status = :interrupted
128
- handle_interrupted_run
129
- else
130
- @status = :successful
131
- handle_successful_run
132
- end
133
-
134
- handle_stderr(scraper_runner.drain_stderr)
135
-
136
- transformers.each do |transformer|
137
- runner = transformer['runner']
138
- handle_stderr(runner.drain_stderr)
139
- end
140
-
141
- rescue ScriptError => e
142
- if @interrupted
143
- @status = :interrupted
144
- handle_interrupted_run
145
- else
146
- @status = :failed
147
- handle_failed_run
148
- end
149
- end
150
- ensure
151
- handle_stderr(scraper_runner.drain_stderr)
152
- scraper_runner.close unless scraper_runner.nil?
153
-
154
- transformers.each do |transformer|
155
- runner = transformer['runner']
156
- if !runner.nil?
157
- handle_stderr(runner.drain_stderr)
158
- runner.close
159
- end
160
- end
161
- end
162
-
163
- def successful?
164
- @status == :successful
165
- end
166
-
167
- def interrupt
168
- @interrupted = true
169
- end
170
-
171
- private
172
- def transformers
173
- @config['transformers'] || []
174
- end
175
-
176
- def validate(record, data_type)
177
- schema = get_schema(data_type)
178
- errors = JSON::Validator.fully_validate(schema, record, :errors_as_objects => true)
179
- messages = errors.map do |error|
180
- case error[:message]
181
- when /The property '#\/' did not contain a required property of '(\w+)'/
182
- "Missing required attribute: #{Regexp.last_match(1)}"
183
- else
184
- error[:message]
185
- end
186
- end
187
-
188
- # if messages.empty?
189
- # identifying_fields = identifying_fields_for_data_type(data_type)
190
- # identifying_hash = record.slice(*identifying_fields)
191
- #
192
- # if identifying_hash.empty?
193
- # messages << "Missing attributes for identifying fields: #{identifying_fields.join(', ')}"
194
- # else
195
- # record_uid = Digest::SHA1.hexdigest(identifying_hash.to_query)
196
- # if @seen_uids.include?(record_uid)
197
- # messages << "Values for identifying fields must be unique. There has already been a record with: #{identifying_hash.to_json}"
198
- # else
199
- # @seen_uids << record_uid
200
- # end
201
- # end
202
- # end
203
-
204
- messages
205
- end
206
-
207
- def identifying_fields_for_data_type(data_type)
208
- if data_type == @config['data_type']
209
- @config['identifying_fields']
210
- else
211
- transformers = @config['transformers'].select {|transformer| transformer['data_type'] == data_type}
212
- raise "Expected to find precisely 1 transformer matching #{data_type} in manifest.json" unless transformers.size == 1
213
- transformers[0]['identifying_fields']
214
- end
215
- end
216
-
217
- def get_schema(data_type)
218
- if !@schemas.has_key?(data_type)
219
- hyphenated_name = data_type.to_s.gsub("_", "-").gsub(" ", "-")
220
- @schemas[data_type] = File.expand_path("../../schema/schemas/#{hyphenated_name}-schema.json", __FILE__)
221
- end
222
-
223
- @schemas[data_type]
224
- end
225
-
226
- def handle_valid_record(record, data_type)
227
- raise NotImplementedError
228
- end
229
-
230
- def handle_invalid_record(record, data_type, errors)
231
- raise NotImplementedError
232
- end
233
-
234
- def handle_non_json_output(line)
235
- raise NotImplementedError
236
- end
237
-
238
- def handle_successful_run
239
- end
240
-
241
- def handle_interrupted_run
242
- end
243
-
244
- def handle_stderr(data)
245
- $stderr.write(data)
246
- end
247
-
248
- def scraper_file
249
- candidates = Dir.glob(File.join(@bot_directory, 'scraper.{rb,py}'))
250
- case candidates.size
251
- when 0
252
- raise 'Could not find scraper to run'
253
- when 1
254
- candidates.first
255
- else
256
- raise "Found multiple scrapers: #{candidates.join(', ')}"
257
- end
258
- end
259
-
260
- def interpreter_for(file)
261
- case file
262
- when /\.rb$/
263
- prerun = File.expand_path("../prerun.rb", __FILE__)
264
- "ruby -r#{prerun}"
265
- when /\.py$/
266
- 'python -u'
267
- else
268
- raise "Could not run #{file}"
269
- end
270
- end
271
- end
272
-
273
- class CommandRunner
274
-
275
- def to_s
276
- "CommandRunner #{@command}, pid #{@wait_thread.pid} (#{@wait_thread.status})"
277
- end
278
-
279
- def initialize(command, opts={})
280
- @command = command
281
- @timeout = opts[:timeout] ||= 3600
282
- @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(command)
283
- end
284
-
285
- def get_next_line
286
- begin
287
- Timeout::timeout(@timeout) { @stdout.gets }
288
- rescue Timeout::Error
289
- raise TurbotRunner::ScriptError.new("#{@command} produced no output for #{@timeout} seconds")
290
- rescue EOFError
291
- raise_if_failed!
292
- return nil
293
- end
294
- end
295
-
296
- def drain_stderr
297
- output = ''
298
- while @stderr.ready?
299
- output += @stderr.read(256)
300
- end
301
- output
302
- end
303
-
304
- def success?
305
- if finished?
306
- @wait_thread.value.success?
307
- end
308
- end
309
-
310
- def failed?
311
- if finished?
312
- !@wait_thread.value.success?
313
- end
314
- end
315
-
316
- def raise_if_failed!
317
- raise TurbotRunner::ScriptError if failed?
318
- end
319
-
320
- def finished?
321
- !@wait_thread.status
322
- end
323
-
324
- def send_line(line)
325
- @stdin.puts(line)
326
- end
327
-
328
- def close
329
- @stdin.close
330
- @stdout.read # drain pipe
331
- @stdout.close
332
- @wait_thread.kill
333
- end
334
- end
335
- end
1
+ require 'turbot_runner/base_handler'
2
+ require 'turbot_runner/processor'
3
+ require 'turbot_runner/runner'
4
+ require 'turbot_runner/script_runner'
5
+ require 'turbot_runner/version'