lrun-ruby 0.1.1

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.
@@ -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