lrun-ruby 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NzllZjNiZGQzZWZhOWVjMjlmOTliNGUxYTExYzE1MzI2ZmQ0ZDc1Yg==
5
+ data.tar.gz: !binary |-
6
+ YzM1YTU5ZjkwNTRlYzE5OTI5NWNmZWFmYzljYzNlNmQ0NTdiYjQ1Yg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MmEwNjQ0NGUwYWM0YWJhMTVkOGI0Y2E4N2ViMmI4MWI5YWMxMjg0N2FiNmQ1
10
+ N2ZkM2I0YmVjZmQ1Mjc2YTc3MWQ0YWRlYTU4MjNmODM3MjI4MTQxMjViZWQ2
11
+ NTc3YzlhMjk3MmQ4MjU2MDQxZTQzNTEwMzY5OWI2ODRkNzI4ODQ=
12
+ data.tar.gz: !binary |-
13
+ MTZmOTE1NTdiNDdhZDkwNzdkYmZmZmRlMWE0ZmJjMzk0NzJmYWM4MDM2MzRj
14
+ NzQyOTQ5ZTk1M2EzMzRjNTM1M2EzZDA3MGI2ZmIwZDY1Mjk4YzFkZDYzZGY1
15
+ Y2I0MmJmMjhkNjYwMWIyMjNhYTlkYWVkMDI3M2JlNjMwYzI0YzA=
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2012-2013 WU Jun <quark@zju.edu.cn>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # lrun-ruby
2
+
3
+ [![Build Status](https://travis-ci.org/quark-zju/lrun-ruby.png)](https://travis-ci.org/quark-zju/lrun-ruby)
4
+ [![Code Climate](https://codeclimate.com/github/quark-zju/lrun-ruby.png)](https://codeclimate.com/github/quark-zju/lrun-ruby)
5
+
6
+ Ruby binding for [lrun](https://github.com/quark-zju/lrun)
7
+
8
+ ## Dependencies
9
+
10
+ * [lrun](https://github.com/quark-zju/lrun)
11
+ * Ruby 1.9 or above
12
+
13
+ ## Installation
14
+
15
+ 1. Install lrun binary. For detailed steps, please refer to [lrun project page](https://github.com/quark-zju/lrun).
16
+ 2. Install `lrun-ruby` gem:
17
+
18
+ ```bash
19
+ gem install lrun-ruby
20
+ ```
21
+
22
+ ## Documentation
23
+
24
+ lrun-ruby is documented with examples.
25
+
26
+ [Browse online](http://rdoc.info/github/quark-zju/lrun-ruby).
27
+
28
+ Alternatively, run `rake doc` and open `doc/index.html`.
29
+
30
+ ## Usage
31
+
32
+ See examples in documentation.
@@ -0,0 +1,10 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'yard'
3
+
4
+ task :default => :spec
5
+
6
+ RSpec::Core::RakeTask.new
7
+ YARD::Rake::YardocTask.new
8
+
9
+ task :doc => [:yard]
10
+ task :test => [:spec]
@@ -0,0 +1,381 @@
1
+ ################################################################################
2
+ # Copyright (C) 2012-2013 WU Jun <quark@zju.edu.cn>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ ################################################################################
22
+
23
+ require 'tempfile'
24
+ require 'shellwords'
25
+
26
+ # Run program using <tt>lrun</tt>. Require external <tt>lrun</tt> binary.
27
+ #
28
+ # @see Lrun.run
29
+ # @see https://github.com/quark-zju/lrun lrun project page
30
+ #
31
+ # = Example
32
+ #
33
+ # Lrun.run('foo', Lrun.merge_options({:max_memory => 2 ** 20, :max_cpu_time => 5}, {:network => false}))
34
+ #
35
+ module Lrun
36
+
37
+ # Name of lrun executable
38
+ LRUN_BINARY = 'lrun'
39
+
40
+ # Full path of lrun executable, automatically detected using {LRUN_BINARY} and <tt>PATH</tt> environment variable
41
+ LRUN_PATH = ENV['PATH'].split(':').map{|p| File.join(p, LRUN_BINARY)}.find{|p| File.executable? p}
42
+
43
+ # Available lrun options, and whether they can occur multiple times (1: no, 2: yes)
44
+ LRUN_OPTIONS = {
45
+ :max_cpu_time => 1,
46
+ :max_real_time => 1,
47
+ :max_memory => 1,
48
+ :max_output => 1,
49
+ :max_nprocess => 1,
50
+ :max_rtprio => 1,
51
+ :max_nfile => 1,
52
+ :max_stack => 1,
53
+ :isolate_process => 1,
54
+ :basic_devices => 1,
55
+ :reset_env => 1,
56
+ :network => 1,
57
+ :chroot => 1,
58
+ :chdir => 1,
59
+ :nice => 1,
60
+ :umask => 1,
61
+ :uid => 1,
62
+ :gid => 1,
63
+ :interval => 1,
64
+ :cgname => 1,
65
+ :bindfs => 2,
66
+ :cgroup_option => 2,
67
+ :tmpfs => 2,
68
+ :env => 2,
69
+ :fd => 2,
70
+ :group => 2,
71
+ :cmd => 2,
72
+ }
73
+
74
+ # Keep how many bytes of stdout and stderr, can be overrided using <tt>options[:truncate]</tt>
75
+ TRUNCATE_OUTPUT_LENGTH = 4096
76
+
77
+ # Error related to lrun
78
+ class LrunError < RuntimeError; end
79
+
80
+ # Result of {Lrun.run}.
81
+ class Result < Struct.new(:memory, :cputime, :exceed, :exitcode, :signal, :stdout, :stderr)
82
+
83
+ # @!attribute memory
84
+ # @return [Integer] peak memory used, in bytes
85
+ #
86
+ # @!attribute cputime
87
+ # @return [Float] CPU time consumed, in seconds
88
+ #
89
+ # @!attribute exceed
90
+ # @return [Symbol] what limit exceed,
91
+ # <tt>:time</tt> if time limit exceeded,
92
+ # <tt>:memory</tt> if memory limit exceeded,
93
+ # <tt>:output</tt> if output limit exceeded,
94
+ # or <tt>nil</tt> if no limit exceeded
95
+ #
96
+ # @!attribute exitcode
97
+ # @return [Integer] exit code
98
+ #
99
+ # @!attribute signal
100
+ # @return [Integer] signal number received, or <tt>nil</tt> if exited normally
101
+ #
102
+ # @!attribute stdout
103
+ # @return [String] standard output, or <tt>nil</tt> if it is redirected in options
104
+ #
105
+ # @!attribute stderr
106
+ # @return [String] standard error output, or <tt>nil</tt> if stderr is redirected in options
107
+
108
+ # @return [Boolean] whether the program exited without crash and has a zero exit code
109
+ def clean?
110
+ exitcode.to_i == 0 && !crashed?
111
+ end
112
+
113
+ # @return [Boolean] whether the program crashed (exited by signal)
114
+ def crashed?
115
+ !signal.nil?
116
+ end
117
+ end
118
+
119
+
120
+ # Merge options so that it can be used in {Lrun.run}.
121
+ #
122
+ # @param [Array<Hash>] options options to be merged
123
+ # @return [Hash] merged options, can be used again in {Lrun.merge_options}
124
+ #
125
+ # = Example
126
+ #
127
+ # Lrun.merge_options({:uid => 1000}, {:gid => 100})
128
+ # # => {:uid=>1000, :gid=>100}
129
+ #
130
+ # Lrun.merge_options({:nice => 1, :uid => 1001}, {:nice => 2})
131
+ # # => {:nice=>2, :uid=>1000}
132
+ #
133
+ # Lrun.merge_options({:fd => [4, 6]}, {:fd => 5}, {:fd => 7})
134
+ # # => {:fd=>[4, 6, 5, 7]}
135
+ #
136
+ # Lrun.merge_options({:env => {'A'=>'1', 'B' => '2'}}, {:env => {'C' => '3'}})
137
+ # # => {:env=>[["A", "1"], ["B", "2"], ["C", "3"]]}
138
+ #
139
+ # Lrun.merge_options({:uid => 1000}, {:uid => nil})
140
+ # # => {}
141
+ #
142
+ # Lrun.merge_options({:fd => [4]}, {:fd => 5}, {:fd => nil})
143
+ # # => {}
144
+ #
145
+ # Lrun.merge_options({:network => true, :chdir => '/tmp', :bindfs => {'/a' => '/b'}},
146
+ # {:network => nil, :bindfs => {'/c' => '/d'}})
147
+ # # => {:chdir=>"/tmp", :bindfs=>[["/a", "/b"], ["/c", "/d"]]}
148
+ def self.merge_options(*options)
149
+ # Remove nil
150
+ options.compact!
151
+
152
+ # Check type of options
153
+ raise TypeError, 'options should be Hash' unless options.all? { |o| o.is_a? Hash }
154
+
155
+ # Merge options
156
+ options.inject({}) do |result, option|
157
+ option.each do |k, v|
158
+ # Remove an option using nil
159
+ if v.nil?
160
+ result.delete k
161
+ next
162
+ end
163
+
164
+ # Append to or Replace an option
165
+ case LRUN_OPTIONS[k]
166
+ when 2
167
+ # Append to previous options
168
+ result[k] ||= []
169
+ result[k] += [*v]
170
+ else
171
+ # Overwrite previous option
172
+ result[k] = v
173
+ end
174
+ end
175
+ result
176
+ end
177
+ end
178
+
179
+ # Run program using <tt>lrun</tt> binary.
180
+ #
181
+ # @param [Array<String>, String] commands
182
+ # commands to be executed
183
+ #
184
+ # @param [Hash] options
185
+ # options for lrun.
186
+ # Besides options in {Lrun.LRUN_OPTIONS}, there are some additional options available:
187
+ #
188
+ # truncate::
189
+ # maximum bytes read for stderr and stdout (default: {Lrun.TRUNCATE_OUTPUT_LENGTH}).
190
+ # stdin::
191
+ # stdin file path (default: no input).
192
+ # stdout::
193
+ # stdout file path (default: a tempfile, will be deleted automatically).
194
+ # If this option is set, the returned result will have no stdout,
195
+ # you should read and delete stdout file manually.
196
+ # stderr::
197
+ # stderr file path (default: a tempfile, will be deleted automatically).
198
+ # If this option is set, the returned result will have no stderr,
199
+ # you should read and delete stderr file manually.
200
+ #
201
+ # Note: lrun chroot and mounts does not affect above paths.
202
+ #
203
+ # @return [Lrun::Result]
204
+ #
205
+ # = Example
206
+ #
207
+ # Lrun.run('echo hello')
208
+ # # => #<struct Lrun::Result
209
+ # # memory=262144, cputime=0.002,
210
+ # # exceed=nil, exitcode=0, signal=nil,
211
+ # # stdout="hello\n", stderr="">
212
+ #
213
+ # Lrun.run('java', :max_memory => 2 ** 19, :stdout => '/tmp/out.txt')
214
+ # # => #<struct Lrun::Result
215
+ # # memory=524288, cputime=0.006,
216
+ # # exceed=:memory, exitcode=0, signal=nil,
217
+ # # stdout=nil, stderr="">
218
+ #
219
+ # Lrun.run('sleep 30', :max_real_time => 1, :stderr => '/dev/null')
220
+ # # => #<struct Lrun::Result
221
+ # # memory=262144, cputime=0.002,
222
+ # # exceed=:time, exitcode=0, signal=nil,
223
+ # # stdout="", stderr=nil>
224
+ #
225
+ # Lrun.run('cat', :max_output => 100, :stdin => '/dev/urandom', :truncate => 2)
226
+ # # => #<struct Lrun::Result
227
+ # # memory=782336, cputime=0.05,
228
+ # # exceed=:output, exitcode=0, signal=nil,
229
+ # # stdout="U\xE1", stderr="">
230
+ #
231
+ def self.run(commands, options = {})
232
+ # Make sure lrun binary is available
233
+ available!
234
+
235
+ # Temp files storing stdout and stderr of target process
236
+ tmp_out = tmp_err = nil
237
+
238
+ # Create temp stdout, stderr files if user does not redirect them
239
+ options = options.dup
240
+ options[:stdout] ||= (tmp_out = Tempfile.new("lrun.#{$$}.out")).path
241
+ options[:stderr] ||= (tmp_err = Tempfile.new("lrun.#{$$}.err")).path
242
+
243
+ IO.pipe do |rfd, wfd|
244
+ # Keep pid of lrun process for checking its status
245
+ pid = spawn_lrun commands, options, wfd
246
+
247
+ # Read fd 3, where lrun write its report
248
+ wfd.close
249
+ report = rfd.read
250
+
251
+ # Check if lrun exits normally
252
+ stat = Process.wait2(pid)[-1]
253
+ if stat.signaled? || stat.exitstatus != 0
254
+ raise LrunError, "lrun exits abnormally: #{stat}. #{tmp_err.read unless tmp_err.nil?}"
255
+ end
256
+
257
+ # Build and return result
258
+ build_result report, tmp_out, tmp_err, options[:truncate]
259
+ end
260
+ ensure
261
+ clean_tmpfile [tmp_out, tmp_err]
262
+ end
263
+
264
+ # Check if lrun binary exists
265
+ #
266
+ # @return [Bool] whether lrun binary is found
267
+ def self.available?
268
+ !LRUN_PATH.nil?
269
+ end
270
+
271
+ # Complain if lrun binary is not available
272
+ def self.available!
273
+ raise LrunError, "#{LRUN_BINARY} not found in PATH. Please install lrun first." unless available?
274
+ end
275
+
276
+ private
277
+
278
+ # Clean temp files
279
+ #
280
+ # @param [Array<Tempfile>] temp_files temp files to be cleaned
281
+ def self.clean_tmpfile(temp_files)
282
+ temp_files.each do |file|
283
+ file.unlink rescue nil
284
+ end
285
+ end
286
+
287
+ # Expand options to be used in command line
288
+ #
289
+ # @param [Hash] options single options hash returned by {Lrun.merge_options}
290
+ # @return [Array<String>] command line arguments
291
+ #
292
+ # = Example
293
+ #
294
+ # Lrun.format_options({:chdir=>"/tmp", :bindfs=>[["/a", "/b"], ["/c", "/d"]], :fd => [2, 3]})
295
+ # # => ["--chdir", "/tmp", "--bindfs", "/a", "/b", "--bindfs", "/c", "/d", "--fd", "2", "--fd", "3"]
296
+ def self.expand_options(options)
297
+ raise TypeError, 'expect options to be a Hash' unless options.is_a? Hash
298
+
299
+ command_arguments = options.map do |key, values|
300
+ expand_option key, values
301
+ end
302
+
303
+ command_arguments.compact.flatten.map(&:to_s)
304
+ end
305
+
306
+ # Expand a single option to be used in command line
307
+ #
308
+ # @param [Symbol] key option name
309
+ # @param [Array, #to_s] values option value(s)
310
+ # @return [Array<String>] arguments used in command line
311
+ def self.expand_option(key, values)
312
+ return nil unless LRUN_OPTIONS.has_key? key
313
+
314
+ [*values].map do |value|
315
+ ["--#{key.to_s.gsub('_', '-')}", *value]
316
+ end
317
+ end
318
+
319
+ # Spawn lrun process.
320
+ #
321
+ # @param [IO:fd] report_fd
322
+ # file descriptor used to receive lrun report
323
+ #
324
+ # @return [Integer] pid spawned process id of lrun
325
+ def self.spawn_lrun(commands, options, report_fd)
326
+ # Expand commands if commands is a string
327
+ commands = Shellwords.split(commands) if commands.is_a? String
328
+ raise ArgumentError, 'commands should not be empty' if commands.nil? || commands.empty?
329
+
330
+ # Build command line
331
+ command_line = [LRUN_PATH, *expand_options(options), *commands]
332
+ spawn_options = {0 => options[:stdin] || :close,
333
+ 1 => options[:stdout] || (tmp_out = Tempfile.new("lrun.#{$$}.out")).path,
334
+ 2 => options[:stderr] || (tmp_err = Tempfile.new("lrun.#{$$}.err")).path,
335
+ 3 => report_fd.fileno}
336
+
337
+ # Keep pid of lrun process for checking its status
338
+ Process.spawn(*command_line, spawn_options)
339
+ end
340
+
341
+ # Build {Lrun::Result} from essential information.
342
+ #
343
+ # @return [Lrun::Result]
344
+ def self.build_result(lrun_report, stdout = nil, stderr = nil, truncate = TRUNCATE_OUTPUT_LENGTH)
345
+ report = Hash[lrun_report.lines.map{ |l| l.chomp.split(' ', 2)}]
346
+
347
+ # Collect information
348
+ memory = report['MEMORY'].to_i
349
+ cputime = report['CPUTIME'].to_f
350
+ exceed = parse_exceed(report['EXCEED'])
351
+ exitcode = report['EXITCODE'].to_i
352
+ signal = report['SIGNALED'].to_i == 0 ? nil : report['TERMSIG'].to_i
353
+ stdout &&= stdout.read(truncate) || ''
354
+ stderr &&= stderr.read(truncate) || ''
355
+
356
+ # Build Result
357
+ Result.new(memory, cputime, exceed, exitcode, signal, stdout, stderr)
358
+ end
359
+
360
+ # Parse exceed information from lrun report
361
+ #
362
+ # @param [String] report_exceed exceed reported by lrun
363
+ # @return [Symbol, nil] exceeded limit in symbol, or <tt>nil</tt> if no limit exceeded
364
+ def self.parse_exceed(report_exceed)
365
+ case report_exceed
366
+ when 'none'
367
+ nil
368
+ when /TIME/
369
+ :time
370
+ when /OUTPUT/
371
+ :output
372
+ when /MEMORY/
373
+ :memory
374
+ else
375
+ raise LrunError, "unexpected EXCEED returned by lrun: #{report['EXCEED']}"
376
+ end
377
+ end
378
+
379
+ # Autoload {Lrun::Runner}
380
+ autoload :Runner, 'lrun/runner'
381
+ end
@@ -0,0 +1,84 @@
1
+ ################################################################################
2
+ # Copyright (C) 2012-2013 WU Jun <quark@zju.edu.cn>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ ################################################################################
22
+
23
+ require 'lrun'
24
+
25
+ # {Lrun} provides essential methods to run program with different options
26
+ # using <tt>lrun</tt> binary.
27
+ #
28
+ # {Lrun::Runner} makes it easier to run many programs with same options.
29
+ #
30
+ # = Example
31
+ #
32
+ # runner = Lrun::Runner.new(:max_cpu_time=>1, :tmpfs=>[["/tmp", 2**20]], :chdir=>"/tmp")
33
+ # # or:
34
+ # runner = Lrun::Runner.new.where(:max_cpu_time=>1, :tmpfs=>[["/tmp", 2**20]], :chdir=>"/tmp")
35
+ # # or:
36
+ # runner = Lrun::Runner.new.max_cpu_time(1).tmpfs('/tmp' => 2**20).chdir('/tmp')
37
+ #
38
+ # runner.options
39
+ # # => {:max_cpu_time=>1, :tmpfs=>[["/tmp", 1048576]], :chdir=>"/tmp"}
40
+ # runner.max_cpu_time(nil).options
41
+ # # => {:tmpfs=>[["/tmp", 1048576]], :chdir=>"/tmp"}
42
+ #
43
+ # runner.run('pwd').stdout
44
+ # # => "/tmp\n"
45
+ # runner.cmd("touch `seq 1 4`").run('ls').stdout
46
+ # # => "1\n2\n3\n4\n"
47
+ # runner.cmd("echo 'puts ENV[?A]' > a.rb").env('A' => 'Hello').run('ruby a.rb').stdout
48
+ # # => "Hello\n"
49
+ class Lrun::Runner
50
+
51
+ # @!attribute [rw] options
52
+ # @return [Hash] options used in {#run}
53
+ attr_accessor :options
54
+
55
+ # @param [Hash] options options for the runner
56
+ def initialize(options = {})
57
+ @options = options
58
+ end
59
+
60
+ # Methods for easily applying custom options
61
+ [:stdin, :stdout, :stderr, *Lrun::LRUN_OPTIONS.keys].each do |name|
62
+ define_method name do |value| where(name => value) end
63
+ end
64
+
65
+ # Run commands using current {#options}.
66
+ #
67
+ # @param [Array<String>, String] commands commands to be executed
68
+ #
69
+ # @see options
70
+ # @see Lrun.run
71
+ def run(commands)
72
+ Lrun.run commands, @options
73
+ end
74
+
75
+ # Create a new runner with new options
76
+ #
77
+ # @param [Hash] options new options to be merged
78
+ # @return [Lrun::Runner] new runner created with merged options
79
+ def where(options)
80
+ raise TypeError, 'expect options to be a Hash' unless options.is_a? Hash
81
+ Lrun::Runner.new(Lrun.merge_options(@options, options))
82
+ end
83
+
84
+ end
@@ -0,0 +1,17 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'lrun-ruby'
3
+ s.version = '0.1.1'
4
+ s.date = Date.civil(2013,3,23)
5
+ s.summary = 'Ruby binding for lrun'
6
+ s.description = 'Ruby binding for lrun, a standalone executable designed to run programs with limited resources under Linux.'
7
+ s.authors = ["Wu Jun"]
8
+ s.email = 'quark@zju.edu.cn'
9
+ s.homepage = 'https://github.com/quark-zju/lrun-ruby'
10
+ s.require_paths = ['lib']
11
+ s.licenses = ['MIT']
12
+ s.has_rdoc = 'yard'
13
+ s.files = %w(LICENSE README.md Rakefile lrun-ruby.gemspec)
14
+ s.files += Dir.glob("lib/**/*.rb")
15
+ s.files += Dir.glob("spec/**/*.rb")
16
+ s.test_files = Dir['spec/**/*.rb']
17
+ end
@@ -0,0 +1,59 @@
1
+ require 'lrun/runner'
2
+
3
+ describe Lrun::Runner, '#new' do
4
+
5
+ it 'creates a runner' do
6
+ Lrun::Runner.new.class.should == Lrun::Runner
7
+ end
8
+
9
+ it 'accepts options' do
10
+ Lrun::Runner.new(:uid => 2, :gid => 3).options.should == {:uid => 2, :gid => 3}
11
+ end
12
+
13
+ it 'rejects too many options' do
14
+ lambda { Lrun::Runner.new({:uid => 2}, {:gid => 3}) }.should raise_error(ArgumentError)
15
+ end
16
+
17
+ end
18
+
19
+ describe Lrun::Runner, '#where' do
20
+
21
+ let(:runner) { Lrun::Runner.new(:uid => 2, :fd => 2, :tmpfs => {'/tmp' => 0}) }
22
+
23
+ it 'change options' do
24
+ runner.where(:uid => 3).options[:uid].should == 3
25
+ end
26
+
27
+ it 'add options' do
28
+ runner.where(:fd => 3).options[:fd].should == [2, 3]
29
+ runner.where(:fd => [4, 5]).options[:fd].should == [2, 4, 5]
30
+ runner.where(:tmpfs => {'/usr/bin' => 1}).options[:tmpfs].should == [["/tmp", 0], ["/usr/bin", 1]]
31
+ end
32
+
33
+ it 'delete options' do
34
+ runner.where(:uid => nil).options[:fd].should_not include(:uid)
35
+ runner.where(:fd => nil).options.keys.should_not include(:fd)
36
+ runner.where(:fd => nil, :uid => nil, :tmpfs => nil).options.should be_empty
37
+ end
38
+
39
+ it 'does not change original options' do
40
+ lambda {
41
+ runner.where(:uid => 5, :fd => nil, :tmpfs => {'/a' => 2}, :gid => 6)
42
+ }.should_not change(runner, :options)
43
+ end
44
+
45
+ end
46
+
47
+ describe Lrun::Runner, '#run', :if => Lrun.available? do
48
+
49
+ let(:runner) { Lrun::Runner.new(:uid => 2, :fd => 2, :tmpfs => {'/tmp' => 0}) }
50
+
51
+ it 'runs' do
52
+ runner.run('echo a').stdout.should == "a\n"
53
+ end
54
+
55
+ it 'passes options' do
56
+ runner.env('A' => 42).run(['sh', '-c', 'echo $A']).stdout.chomp.should == '42'
57
+ end
58
+
59
+ end
@@ -0,0 +1,144 @@
1
+ require 'lrun'
2
+ require 'tempfile'
3
+
4
+ describe Lrun do
5
+
6
+ it 'can not be instantiated' do
7
+ lambda { Lrun.new }.should raise_error(NoMethodError)
8
+ end
9
+
10
+ end
11
+
12
+ describe Lrun, '#merge_options' do
13
+
14
+ it 'accepts empty options' do
15
+ Lrun.merge_options(nil).should == {}
16
+ Lrun.merge_options({}).should == {}
17
+ end
18
+
19
+ it 'rejects non-Hash' do
20
+ lambda { Lrun.merge_options(1) }.should raise_error(TypeError)
21
+ lambda { Lrun.merge_options('a', 'b') }.should raise_error(TypeError)
22
+ end
23
+
24
+ it 'merges options' do
25
+ Lrun.merge_options({:uid => 1000}, {:gid => 100, :interval => 2}, {:network => false}).should == {:network => false, :uid => 1000, :gid => 100, :interval => 2}
26
+ Lrun.merge_options({:fd => [4, 6]}, {:fd => 5}, {:fd => 7}).should == {:fd=>[4, 6, 5, 7]}
27
+ Lrun.merge_options({:bindfs => {'/a' => '/b'}}, {:bindfs => {'/c' => '/d'}}).should == {:bindfs => [["/a", "/b"], ["/c", "/d"]]}
28
+ end
29
+
30
+ it 'overwrite options' do
31
+ Lrun.merge_options({:uid => 1}, {:uid => 3}, {:uid => 4}).should == {:uid => 4}
32
+ end
33
+
34
+ it 'removes options' do
35
+ Lrun.merge_options({:uid => 1000}, {:uid => nil}).should == {}
36
+ Lrun.merge_options({:fd => [4, 5, 6]}, {:fd => 7}, {:fd => nil}).should == {}
37
+ end
38
+
39
+ end
40
+
41
+ describe Lrun, '#run', :if => Lrun.available? do
42
+
43
+ context "when running true and false" do
44
+ let(:true_result) { Lrun.run('true') }
45
+ let(:false_result) { Lrun.run('false') }
46
+
47
+ it 'returns exitcode' do
48
+ true_result.exitcode.should == 0
49
+ false_result.exitcode.should_not == 0
50
+ end
51
+
52
+ it 'returns cpu and memory usage' do
53
+ true_result.memory.should > 0
54
+ true_result.cputime.should >= 0
55
+ end
56
+
57
+ it 'returns exceed' do
58
+ true_result.exceed.should be_nil
59
+ false_result.exceed.should be_nil
60
+ end
61
+
62
+ it 'returns stdout and stderr' do
63
+ true_result.stdout.should == ""
64
+ false_result.stdout.should == ""
65
+ end
66
+
67
+ it 'returns stderr' do
68
+ true_result.stderr.should == ""
69
+ false_result.stderr.should == ""
70
+ end
71
+
72
+ it '.clean?' do
73
+ true_result.clean?.should be_true
74
+ false_result.clean?.should be_false
75
+ end
76
+
77
+ it '.crashed?' do
78
+ true_result.crashed?.should be_false
79
+ false_result.crashed?.should be_false
80
+ end
81
+ end
82
+
83
+ context 'when running cat' do
84
+ it 'can redirect stdin' do
85
+ begin
86
+ tmpfile = Tempfile.new("input")
87
+ tmpfile.write("foo bar")
88
+ tmpfile.close
89
+ Lrun.run('cat', stdin: tmpfile.path).stdout.should == "foo bar"
90
+ ensure
91
+ tmpfile.unlink rescue nil
92
+ end
93
+ end
94
+
95
+ it 'does not alter options' do
96
+ options = {}
97
+ Lrun.run('cat', options)
98
+ options.should == {}
99
+ end
100
+ end
101
+
102
+ context 'when running echo' do
103
+ it 'captures stdout' do
104
+ Lrun.run('echo Hello').stdout.should == "Hello\n"
105
+ Lrun.run(['echo', 'World']).stdout.should == "World\n"
106
+ end
107
+
108
+ it 'can redirect stdout' do
109
+ begin
110
+ tmpfile = Tempfile.new("output")
111
+ result = Lrun.run('echo blabla', stdout: tmpfile.path)
112
+ result.stderr.should == ""
113
+ result.stdout.should be_nil
114
+ tmpfile.read.should == "blabla\n"
115
+ ensure
116
+ tmpfile.unlink rescue nil
117
+ end
118
+ end
119
+ end
120
+
121
+ context 'when setting limit options' do
122
+ it 'returns exceed' do
123
+ Lrun.run('sleep 1', :max_real_time => 0.1).exceed.should == :time
124
+ Lrun.run(['cat', '/dev/full'], :stdout => '/dev/null', :max_cpu_time => 0.1).exceed.should == :time
125
+ Lrun.run(['cpp', '/dev/full', '-o', '/dev/null'], :max_memory => 1_000_000).exceed.should == :memory
126
+ Lrun.run(['cat', '/dev/full'], :stdout => '/dev/null', :max_output => 1_000).exceed.should == :output
127
+ end
128
+ end
129
+
130
+ context 'when parameters are illegal' do
131
+ it 'rejects empty command' do
132
+ lambda { Lrun.run(nil) }.should raise_error(ArgumentError)
133
+ lambda { Lrun.run('') }.should raise_error(ArgumentError)
134
+ lambda { Lrun.run([]) }.should raise_error(ArgumentError)
135
+ end
136
+
137
+ it 'handles lrun errors' do
138
+ lambda { Lrun.run('strange_non_existed_executable') }.should raise_error(Lrun::LrunError)
139
+ lambda { Lrun.run(['--unsupported-options', 'true']) }.should raise_error(Lrun::LrunError)
140
+ end
141
+ end
142
+
143
+ end
144
+
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lrun-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Wu Jun
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-23 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Ruby binding for lrun, a standalone executable designed to run programs
14
+ with limited resources under Linux.
15
+ email: quark@zju.edu.cn
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - Rakefile
23
+ - lrun-ruby.gemspec
24
+ - lib/lrun.rb
25
+ - lib/lrun/runner.rb
26
+ - spec/lrun_spec.rb
27
+ - spec/lrun_runner_spec.rb
28
+ homepage: https://github.com/quark-zju/lrun-ruby
29
+ licenses:
30
+ - MIT
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 2.0.3
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Ruby binding for lrun
52
+ test_files:
53
+ - spec/lrun_spec.rb
54
+ - spec/lrun_runner_spec.rb
55
+ has_rdoc: yard