rubish 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,74 @@
1
+ class Rubish::UnixExecutable < Rubish::Executable
2
+ EIO = Rubish::Executable::ExecutableIO
3
+
4
+ class UnixJob < Rubish::Job
5
+ attr_reader :pids
6
+ attr_reader :goods
7
+ attr_reader :bads
8
+
9
+ class BadExit < RuntimeError
10
+ attr_reader :exitstatuses
11
+ def initialize(exitstatuses)
12
+ @exitstatuses = exitstatuses
13
+ end
14
+ end
15
+
16
+ def initialize(exe)
17
+ # prepare_io returns an instance of ExeIO
18
+ @ios = EIO.ios([exe.i || Rubish::Context.current.i,"r"],
19
+ [exe.o || Rubish::Context.current.o,"w"],
20
+ [exe.err || Rubish::Context.current.err,"w"])
21
+ i,o,err = @ios
22
+ @pids = exe.exec_with(i.io,o.io,err.io)
23
+ __start
24
+ end
25
+
26
+ def wait
27
+ raise Rubish::Error.new("already waited") if self.done?
28
+ begin
29
+ exits = self.pids.map do |pid|
30
+ Process.wait(pid)
31
+ $?
32
+ end
33
+ @ios.each do |io|
34
+ io.close
35
+ end
36
+ @goods, @bads = exits.partition { |status| status.exitstatus == 0}
37
+ @result = goods # set result to processes that exit properly
38
+ if !bads.empty?
39
+ raise Rubish::Job::Failure.new(self,BadExit.new(bads))
40
+ else
41
+ return self.result
42
+ end
43
+ ensure
44
+ __finish
45
+ end
46
+ end
47
+
48
+ def stop(sig="TERM")
49
+ self.pids.each do |pid|
50
+ Process.kill(sig,pid)
51
+ end
52
+ self.wait
53
+ end
54
+
55
+ def stop!
56
+ self.stop("KILL")
57
+ end
58
+
59
+ end
60
+
61
+ def exec!
62
+ UnixJob.new(self)
63
+ end
64
+
65
+ # TODO catch interrupt here
66
+ def exec
67
+ exec!.wait
68
+ end
69
+
70
+ def exec_with(i,o,e)
71
+ raise "abstract"
72
+ end
73
+
74
+ end
@@ -0,0 +1,211 @@
1
+ class Rubish::Workspace < Rubish::Mu
2
+
3
+ class << self
4
+ # this is the default workspace (used by the singleton context
5
+ def singleton
6
+ @singleton ||= Rubish::Workspace.new
7
+ end
8
+
9
+ alias_method :global, :singleton
10
+ end
11
+
12
+ module Base
13
+
14
+ # TODO move this to context?
15
+ def cd(dir,&block)
16
+ if block
17
+ begin
18
+ old_dir = FileUtils.pwd
19
+ FileUtils.cd File.expand_path(dir)
20
+ # hmmm.. calling instance_eval has weird effects, dunno why
21
+ #self.instance_eval &block
22
+ return block.call
23
+ ensure
24
+ FileUtils.cd old_dir
25
+ end
26
+ else
27
+ FileUtils.cd File.expand_path(dir)
28
+ end
29
+ end
30
+
31
+ def puts(*args)
32
+ current_context.o.puts(*args)
33
+ return Rubish::Null
34
+ end
35
+
36
+ def pp(obj)
37
+ PP.pp(obj,current_context.o)
38
+ return Rubish::Null
39
+ end
40
+
41
+ def cmd(method,*args)
42
+ Rubish::Command.new(method,args)
43
+ end
44
+
45
+ def p(cmds_or_workspace=nil,&block)
46
+ # self is the workspace
47
+ case cmds_or_workspace
48
+ when Array
49
+ raise "build pipe from an array of commands or a block" if block
50
+ cmds = cmds_or_workspace
51
+ Rubish::Pipe.new(cmds)
52
+ when Rubish::Workspace
53
+ workspace = cmds_or_workspace
54
+ Rubish::Pipe.build(workspace.derive,&block)
55
+ when nil
56
+ # actually redundant (since pipe builder
57
+ # uses the current workspace when given
58
+ # workspace is nil anyway. But it's nice
59
+ # to be explicit).
60
+ Rubish::Pipe.build(current_workspace.derive,&block)
61
+ end
62
+ end
63
+
64
+ def exec(*exes)
65
+ __exec(:exec,exes)
66
+ end
67
+
68
+ def exec!(*exes)
69
+ __exec(:exec!,exes)
70
+ end
71
+
72
+ # current context on the dynamic context stack
73
+ def current_context
74
+ Rubish::Context.current
75
+ end
76
+ alias_method :context, :current_context
77
+
78
+ def current_workspace
79
+ Rubish::Context.current.workspace
80
+ end
81
+ alias_method :workspace, :current_workspace
82
+
83
+
84
+ # TODO should clone a context (as well as workspace)
85
+ def with(ws_or_context=nil,i=nil,o=nil,e=nil,&block)
86
+ case ws_or_context
87
+ when Rubish::Workspace
88
+ ws = ws_or_context
89
+ # derive from current context but use a different workspace
90
+ c = current_context.derive(ws,i,o,e)
91
+ when Rubish::Context
92
+ parent = ws_or_context
93
+ c = parent.derive(nil,i,o,e)
94
+ when nil
95
+ c = current_context.derive(nil,i,o,e)
96
+ else
97
+ raise "can't create scope with: #{ws_or_context}"
98
+ end
99
+ if block
100
+ c.eval &block
101
+ else
102
+ c
103
+ end
104
+ end
105
+ alias_method :scope, :with
106
+
107
+ def batch(ws_or_context=nil,i=nil,o=nil,e=nil,&block)
108
+ Rubish::BatchExecutable.new(with(ws_or_context,i,o,e),&block)
109
+ end
110
+
111
+ # job control methods
112
+ def wait(*jobs)
113
+ job_control.wait(*jobs)
114
+ end
115
+
116
+ def waitall
117
+ job_control.waitall
118
+ end
119
+
120
+ def stop(job)
121
+ job_control.stop(job)
122
+ end
123
+
124
+ def jobs
125
+ job_control.jobs
126
+ end
127
+
128
+ def job_control
129
+ current_context.job_control
130
+ end
131
+
132
+ private
133
+
134
+ def __exec(exec_method,exes)
135
+ exes.map do |exe|
136
+ raise "not an exeuctable: #{exe}" unless exe.is_a?(Rubish::Executable)
137
+ exe.send(exec_method)
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+ include Base
144
+
145
+ attr_accessor :command_factory_hook
146
+
147
+ def initialize
148
+ # this is a hack for pipe... dunno if there's a better way to do it.
149
+ @command_factory_hook = nil
150
+ @modules = []
151
+ end
152
+
153
+ def extend(*modules,&block)
154
+ @modules.concat modules
155
+ modules.each do |m|
156
+ self.__extend(m)
157
+ end
158
+ # extend with anonymous module
159
+ if block
160
+ mod = Module.new(&block)
161
+ self.__extend mod
162
+ @modules << mod
163
+ end
164
+ self
165
+ end
166
+
167
+ # creates a cloned workspace
168
+ def derive(*modules,&block)
169
+ new_ws = self.__clone
170
+ new_ws.extend(*modules,&block)
171
+ end
172
+
173
+ def eval(__string=nil,&__block)
174
+ raise "should be either a string or a block" if __string && __block
175
+ if __block
176
+ self.__instance_eval(&__block)
177
+ else
178
+ self.__instance_eval(__string)
179
+ end
180
+ end
181
+
182
+ def method_missing(method,*args,&block)
183
+ cmd = Rubish::Command.new(method,args)
184
+ if @command_factory_hook.is_a?(Proc)
185
+ @command_factory_hook.call(cmd)
186
+ else
187
+ cmd
188
+ end
189
+ end
190
+
191
+ def clone
192
+ self.__clone
193
+ end
194
+
195
+ def methods
196
+ self.__methods.reject { |m| m =~ /^__/ }
197
+ end
198
+
199
+ def to_s
200
+ "#<#{self.__class}:#{self.__object_id}>"
201
+ end
202
+
203
+ def inspect
204
+ to_s
205
+ end
206
+
207
+ def to_ary
208
+ [to_s]
209
+ end
210
+
211
+ end
data/test/slowcat.rb ADDED
@@ -0,0 +1,5 @@
1
+ $stdin.each do |l|
2
+ $stdout.puts l
3
+ $stdout.flush
4
+ sleep(1)
5
+ end
data/test/test.rb ADDED
@@ -0,0 +1,896 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # note that report of assertions count is
4
+ # zero. Probably because we are doing assert in
5
+ # workspace rather than Test
6
+
7
+ require File.dirname(__FILE__) + '/../lib/rubish'
8
+ require 'rubygems'
9
+ require 'pp'
10
+ require 'test/unit'
11
+ require 'thread'
12
+ gem 'thoughtbot-shoulda'
13
+ require 'shoulda'
14
+
15
+ require 'set'
16
+
17
+ if ARGV.first == "dev"
18
+ TUT_ = Test::Unit::TestCase
19
+ # create a dummy empty case to disable all tests
20
+ # except the one we are developing
21
+ class TUT
22
+ def self.should(*args,&block)
23
+ nil
24
+ end
25
+
26
+ def self.context(*args,&block)
27
+ nil
28
+ end
29
+ end
30
+ else
31
+ TUT = Test::Unit::TestCase
32
+ TUT_ = Test::Unit::TestCase
33
+ end
34
+
35
+
36
+ TEST_DIR = File.expand_path(File.dirname(__FILE__)) + "/tmp"
37
+
38
+
39
+ RSH = Rubish::Context.global.derive
40
+ RSH.workspace.extend(Test::Unit::Assertions)
41
+ def rsh(&__block)
42
+ if __block
43
+ RSH.eval {
44
+ begin
45
+ self.eval(&__block)
46
+ ensure
47
+ waitall
48
+ end
49
+ }
50
+ else
51
+ return RSH
52
+ end
53
+ end
54
+
55
+ def setup_tmp
56
+ rsh {
57
+ rm(:rf, TEST_DIR).exec if File.exist?(TEST_DIR)
58
+ mkdir(TEST_DIR).exec
59
+ cd TEST_DIR
60
+ }
61
+ end
62
+
63
+ setup_tmp
64
+
65
+
66
+ module Helper
67
+ class << self
68
+ def cat(data)
69
+ rsh {
70
+ cat.i { |p| p.puts data}
71
+ }
72
+ end
73
+
74
+ def time_elapsed
75
+ t1 = Time.now
76
+ yield
77
+ return Time.now - t1
78
+ end
79
+
80
+ def slowcat(n)
81
+ rsh {
82
+ lines = (1..n).to_a
83
+ ruby("../slowcat.rb").i { |p| p.puts lines }
84
+ }
85
+ end
86
+
87
+ def workspace
88
+ # a custom workspace extended with two methods and assertions
89
+ ws = Rubish::Workspace.new.extend Module.new {
90
+ def foo1
91
+ 1
92
+ end
93
+
94
+ def foo2
95
+ 2
96
+ end
97
+ }, Test::Unit::Assertions
98
+ end
99
+
100
+ def context(i=nil,o=nil,e=nil)
101
+ Rubish::Context.singleton.derive(nil,i,o,e)
102
+ end
103
+ end
104
+ end
105
+
106
+ module IOHelper
107
+ class << self
108
+ def created_ios
109
+ set1 = Set.new
110
+ set2 = Set.new
111
+ ObjectSpace.each_object(IO) { |o| set1 << o }
112
+ yield
113
+ ObjectSpace.each_object(IO) { |o| set2 << o }
114
+ set2 - set1
115
+ end
116
+ end
117
+ end
118
+
119
+
120
+ class Rubish::Test < TUT
121
+
122
+ def setup
123
+ setup_tmp
124
+ end
125
+
126
+ should "not have changed directory" do
127
+ rsh {
128
+ assert_equal TEST_DIR, pwd.first
129
+ mkdir("dir").exec
130
+ cd "dir" do
131
+ assert_equal "#{TEST_DIR}/dir", pwd.first
132
+ end
133
+ assert_equal TEST_DIR, pwd.first
134
+ }
135
+ end
136
+
137
+ should "have changed directory" do
138
+ rsh {
139
+ assert_equal TEST_DIR, pwd.first
140
+ mkdir("dir").exec
141
+ cd "dir"
142
+ assert_equal "#{TEST_DIR}/dir", pwd.first
143
+ cd TEST_DIR
144
+ assert_equal TEST_DIR, pwd.first
145
+ }
146
+ end
147
+ end
148
+
149
+
150
+ class Rubish::Test::Workspace < TUT
151
+ # Remember that Object#methods of Workspace
152
+ # instances are aliased with the prefix '__'
153
+
154
+ def setup
155
+ setup_tmp
156
+ end
157
+
158
+ should "alias Object#methods" do
159
+ rsh {
160
+ ws = current_workspace
161
+ assert_instance_of Rubish::Command, ws.class
162
+ # it's somewhat surprising that
163
+ # assert_instance_of still works. Probably not
164
+ # using Object#class but case switching.
165
+ assert_instance_of Rubish::Workspace, ws
166
+ assert_instance_of Class, ws.__class
167
+
168
+ # the magic methods should still be there
169
+ assert ws.__respond_to?(:__id__)
170
+ assert ws.__respond_to?(:__send__)
171
+
172
+ # the magic methods should be aliased as well
173
+ assert ws.__respond_to?(:____id__)
174
+ assert ws.__respond_to?(:____send__)
175
+ }
176
+
177
+
178
+ end
179
+
180
+ should "not introduce bindings to parent workspace" do
181
+ rsh {
182
+ parent = current_workspace
183
+ child = parent.derive {
184
+ def foo
185
+ 1
186
+ end
187
+ }
188
+ child.eval {
189
+ assert_not_equal parent.__object_id, child.__object_id
190
+ # the derived workspace should have the
191
+ # injected binding via its singleton module.
192
+ assert_equal 1, foo
193
+ parent.eval {
194
+ assert_instance_of Rubish::Command, foo, "the original of derived workspace should not respond to injected bindings"
195
+ }
196
+ }
197
+ }
198
+ end
199
+ end
200
+
201
+ class Rubish::Test::Workspace::Base < TUT
202
+ def self
203
+ setup_tmp
204
+ end
205
+
206
+ should "nest with's" do
207
+ rsh {
208
+ c1 = self
209
+ with {
210
+ ws2 = self
211
+ # redefines foo each time this block is executed
212
+ def foo
213
+ 1
214
+ end
215
+
216
+ assert_equal c1, context.parent
217
+ assert_instance_of Rubish::Command, ls
218
+ assert_equal 1, foo
219
+ with {
220
+ assert_equal c1, context.parent.parent
221
+ assert_equal ws2, context.workspace
222
+ assert_equal 1, foo
223
+ acc = []
224
+ c2 = with(current_workspace.derive {def foo; 3 end})
225
+ c2.eval {
226
+ assert_equal 3, foo
227
+ }
228
+ c2.eval {def foo; 33; end}
229
+ c2.eval {assert_equal 33, foo}
230
+
231
+ with(c1) { # explicitly derive from a specified context
232
+ assert_equal c1, context.parent, "should derive from given context"
233
+ }}}
234
+ assert_instance_of Rubish::Command, foo
235
+ }
236
+ end
237
+
238
+ end
239
+
240
+ class Rubish::Test::IO < TUT
241
+
242
+ def setup
243
+ setup_tmp
244
+ end
245
+
246
+ should "chomp lines for each/map" do
247
+ rsh {
248
+ ints = (1..100).to_a.map { |i| i.to_s }
249
+ cat.o("output").i { |p| p.puts(ints)}.exec
250
+ # raw access to pipe would have newlines
251
+ cat.i("output").o do |p|
252
+ p.each { |l| assert l.chomp!
253
+ }
254
+ end.exec
255
+ # iterator would've chomped the lines
256
+ cat.i("output").each do |l|
257
+ assert_nil l.chomp!
258
+ end
259
+ }
260
+ end
261
+
262
+ should "redirect io" do
263
+ rsh {
264
+ ints = (1..100).to_a.map { |i| i.to_s }
265
+ cat.o("output").i { |p| p.puts(ints)}.exec
266
+ assert_equal ints, cat.i("output").map
267
+ assert_equal ints, p { cat; cat; cat}.i("output").map
268
+ assert_equal ints, cat.i { |p| p.puts(ints) }.map
269
+ }
270
+ end
271
+
272
+ should "close pipes used for io redirects" do
273
+ rsh {
274
+ ios = IOHelper.created_ios do
275
+ cat.i { |p| p.puts "foobar" }.o { |p| p.readlines }.exec
276
+ end
277
+ assert ios.all? { |io| io.closed? }
278
+ ios = IOHelper.created_ios do
279
+ cat.i { |p| p.puts "foobar" }.o("output").exec
280
+ end
281
+ assert ios.all? { |io| io.closed? }
282
+ }
283
+ end
284
+
285
+ should "not close stdioe" do
286
+ rsh {
287
+ assert_not $stdin.closed?
288
+ assert_not $stdout.closed?
289
+ assert_not $stderr.closed?
290
+ ios = IOHelper.created_ios do
291
+ ls.exec
292
+ end
293
+ assert ios.empty?
294
+ assert_not $stdin.closed?
295
+ assert_not $stdout.closed?
296
+ assert_not $stderr.closed?
297
+ }
298
+ end
299
+
300
+ should "not close io if redirecting to existing IO object" do
301
+ rsh {
302
+ begin
303
+ f = File.open("/dev/null","w")
304
+ ios = IOHelper.created_ios do
305
+ ls.o(f).exec
306
+ end
307
+ assert ios.empty?
308
+ assert_not f.closed?
309
+ ensure
310
+ f.close
311
+ end
312
+ }
313
+ end
314
+
315
+
316
+ end
317
+
318
+ class Rubish::Test::Executable < TUT
319
+
320
+ def setup
321
+ setup_tmp
322
+ end
323
+
324
+ should "set result to good exits" do
325
+ rsh {
326
+ r = cat.i { |p| p.puts 1}.exec
327
+ assert_equal 1, r.size
328
+ assert_equal 0, r.first.exitstatus
329
+ }
330
+ end
331
+
332
+ should "head,first/tail,last" do
333
+ rsh {
334
+ ls_in_order = p { ls; sort :n }
335
+ files = (1..25).to_a.map { |i| i.to_s }
336
+ exec touch(files)
337
+ assert_equal 25, ls.map.size
338
+ assert_equal 1, ls.head.size
339
+ assert_equal "1", ls_in_order.first
340
+ assert_equal \
341
+ (1..10).to_a.map { |i| i.to_s },
342
+ ls_in_order.head(10)
343
+ assert_equal 25, ls.head(100).size
344
+
345
+ assert_equal 1, ls.tail.size
346
+ assert_equal "25", ls_in_order.last
347
+ assert_equal \
348
+ (16..25).to_a.map { |i| i.to_s },
349
+ ls_in_order.tail(10)
350
+ assert_equal 25, ls.tail(100).size
351
+ }
352
+ end
353
+
354
+ should "quote exec arguments" do
355
+ rsh {
356
+ files = ["a b","c d"]
357
+ # without quoting
358
+ exec touch(files)
359
+ assert_equal 4, ls.map.size
360
+ exec rm(files)
361
+ assert_equal 0, ls.map.size
362
+ # with quoting
363
+ exec touch(files).q
364
+ assert_equal 2, ls.map.size
365
+ exec rm(files).q
366
+ assert_equal 0, ls.map.size
367
+
368
+ }
369
+ end
370
+
371
+ should "raise when exit status not zero" do
372
+ rsh {
373
+
374
+ r = assert_raise(Rubish::Job::Failure) {
375
+ foobarqux_is_no_command.exec
376
+ }
377
+
378
+ begin
379
+ foobarqux_is_no_command.exec
380
+ rescue Rubish::Job::Failure => e
381
+ j = e.job
382
+ assert j.done?
383
+ assert jobs.empty?
384
+ # the result should be the processes that
385
+ # exit properly. in this case, the empty
386
+ # array.
387
+ assert j.result.empty?
388
+ assert_equal 1, e.reason.exitstatuses.size
389
+ assert_not_equal 0, e.reason.exitstatuses.first.exitstatus
390
+ end
391
+
392
+ }
393
+ end
394
+
395
+ end
396
+
397
+ class Rubish::Test::Pipe < TUT
398
+ def setup
399
+ setup_tmp
400
+ end
401
+
402
+ should "build pipe with a block in workspace" do
403
+ rsh {
404
+ pipe = p { cat ; cat ; cat}
405
+ assert_instance_of Rubish::Pipe, pipe
406
+ assert_equal 3, pipe.cmds.length
407
+ assert_equal 1, pipe.i { |p| p.puts 1 }.first.to_i
408
+
409
+ # specify a workspace to build pipe with
410
+ pipe2 = Rubish::Pipe.build(current_workspace.derive { def foo; abcde; end}) {
411
+ foo
412
+ foo
413
+ }
414
+ assert_equal 2, pipe2.cmds.length
415
+ assert_equal "abcde", pipe2.cmds.first.cmd
416
+ }
417
+ end
418
+
419
+ should "build pipe with an array" do
420
+ rsh {
421
+ # tee to 10 files along the pipeline
422
+ tees = (1..10).map { |i| tee "o#{i}" }
423
+ p(tees).i { |p| p.puts "1" }.exec
424
+ assert_equal 10, ls.map.length
425
+ (1..10).map { |i|
426
+ assert_equal 1, cat("o#{i}").first.to_i
427
+ }
428
+ }
429
+ end
430
+ end
431
+
432
+ class Rubish::Test::Streamer < TUT
433
+ def setup
434
+ setup_tmp
435
+ end
436
+
437
+ should "streamer should capture executable's output" do
438
+ rsh {
439
+ cataa = Helper.cat("aa")
440
+ output = cataa.o
441
+ assert_equal "aa", cataa.sed {p}.first
442
+ assert_equal output, cataa.o
443
+ }
444
+ end
445
+
446
+ should "allow streamer chain" do
447
+ rsh {
448
+ assert_equal "aa", Helper.cat("aa").sed { p }.sed { p }.sed { p }.first
449
+ }
450
+ end
451
+
452
+ should "sed with s and gs" do
453
+ rsh {
454
+ # aa => iia => eyeeyea
455
+ assert_equal "eyeeyea", Helper.cat("aa").sed { s /a/, "ii"; gs /i/, "eye"; p}.first
456
+ }
457
+ end
458
+
459
+ should "peek" do
460
+ rsh {
461
+ rs = Helper.cat((1..10).to_a).awk {
462
+ collect(:three,[line,*peek(2)])
463
+ }.end { three }.exec
464
+ assert_equal [["1", "2", "3"],
465
+ ["2", "3", "4"],
466
+ ["3", "4", "5"],
467
+ ["4", "5", "6"],
468
+ ["5", "6", "7"],
469
+ ["6", "7", "8"],
470
+ ["7", "8", "9"],
471
+ ["8", "9", "10"],
472
+ ["9", "10"],
473
+ ["10"]], rs
474
+ }
475
+ end
476
+
477
+ should "skip" do
478
+ rsh {
479
+ rs = Helper.cat((1..10).to_a).awk {
480
+ collect(:three,[line,*peek(2)])
481
+ skip(2)
482
+ }.end { three }.exec
483
+ assert_equal [["1", "2", "3"],
484
+ ["4", "5", "6"],
485
+ ["7", "8", "9"],
486
+ ["10"]], rs
487
+
488
+ }
489
+ end
490
+
491
+ should "trigger by position" do
492
+ assert_equal "1", Helper.cat((1..10).to_a).sed(:bof){p}.first
493
+ assert_equal "10", Helper.cat((1..10).to_a).sed(:eof){p}.first
494
+ assert_equal "1", Helper.cat((1..10).to_a).sed(1){p}.first
495
+ assert_equal "10", Helper.cat((1..10).to_a).sed(-1){p}.first
496
+ assert_equal ["1","10"], Helper.cat((1..10).to_a).sed(/1/){p}.map
497
+ rs = Helper.cat((1..10).to_a).sed(1) { p "a1"; done}.act { p "b" + line }.map
498
+ assert_equal rs, ["a1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "b10"]
499
+ end
500
+
501
+ should "trigger by range" do
502
+ assert_equal ["3","4","5"], Helper.cat((1..10).to_a).sed(3,5) { p }.map
503
+ assert_equal ["8","9","10"], Helper.cat((1..10).to_a).sed(8,:eof) { p }.map
504
+ assert_equal \
505
+ ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
506
+ Helper.cat((1..10).to_a).sed(:bof,:eof) { p }.map
507
+ assert_equal ["b","c","b"], Helper.cat(["a","a","b","c","b","a","a"]).sed(/b/,/b/) { p }.map
508
+ end
509
+
510
+ end
511
+
512
+
513
+ class Rubish::Test::Context < TUT
514
+ def setup
515
+ setup_tmp
516
+ end
517
+
518
+ should "stack contexts" do
519
+ c1 = Helper.context(nil,"c1_out")
520
+ c2 = Helper.context(nil,"c2_out")
521
+ c1.eval {
522
+ # the following "context" is a binding
523
+ # introduced by the default workspace. It
524
+ # should point to the current active context.
525
+ assert_instance_of Rubish::Context, context
526
+ assert_equal Rubish::Context.current, context
527
+ assert_equal Rubish::Context.singleton, context.parent
528
+ assert_equal c1, context
529
+ assert_equal "c1_out", context.o
530
+ c2.eval {
531
+ assert_equal c2, context
532
+ assert_equal "c2_out", context.o
533
+ assert_equal Rubish::Context.singleton, context.parent
534
+ }
535
+ }
536
+ end
537
+
538
+ should "use context specific workspace" do
539
+ Helper.context.eval {
540
+ assert_equal 1, foo1
541
+ assert_equal 2, foo2
542
+ cmd = ls
543
+ assert_instance_of Rubish::Command, cmd
544
+ }
545
+ end
546
+
547
+ should "use context specific IO" do
548
+ output = "context-output"
549
+ c = Helper.context(nil,output)
550
+ c.eval {
551
+
552
+ assert_equal output, Rubish::Context.current.o
553
+ cat.i { |p| p.puts 1}.exec
554
+ assert_equal 1, cat.i(output).first.to_i
555
+ }
556
+ end
557
+
558
+ should "set parent context when deriving" do
559
+ c1 = Rubish::Context.singleton
560
+ c11 = c1.derive
561
+ c111 = c11.derive
562
+ c12 = c1.derive
563
+ c2 = Rubish::Context.new(Rubish::Workspace.new)
564
+
565
+ assert_nil c1.parent
566
+ assert_equal c1, c11.parent
567
+ assert_equal c1, c12.parent
568
+ assert_equal c11, c111.parent
569
+
570
+ assert_nil c2.parent
571
+
572
+
573
+ end
574
+
575
+ should "derive context, using the context attributes of the original" do
576
+ i1 = "i1"
577
+ o1 = "o1"
578
+ e1 = "e1"
579
+ orig = Helper.context(i1, o1, e1)
580
+ derived = orig.derive
581
+
582
+ assert_not_equal orig, derived
583
+ assert_equal orig.workspace, derived.workspace
584
+ assert_equal orig.i, derived.i
585
+ assert_equal orig.o, derived.o
586
+ assert_equal orig.err, derived.err
587
+ assert_not_equal orig.job_control, derived.job_control, "derived context should have its own job control"
588
+
589
+ # make changes to the derived context
590
+ derived.i = "i2"
591
+ derived.o = "o2"
592
+ derived.err = "e2"
593
+
594
+ derived.workspace = Rubish::Workspace.new.derive {
595
+ def foo
596
+ 1
597
+ end
598
+ }
599
+ assert_equal 1, derived.eval { foo }
600
+
601
+ # orig should not have changed
602
+ assert_equal i1, orig.i
603
+ assert_equal o1, orig.o
604
+ assert_equal e1, orig.err
605
+ assert_instance_of Rubish::Command, orig.eval { foo }
606
+
607
+ end
608
+
609
+ should "use context specific job_controls" do
610
+ rsh {
611
+ jc1 = job_control
612
+ slow = Helper.slowcat(1)
613
+ j1 = slow.exec!
614
+
615
+ jc2, j2 = nil
616
+ with {
617
+ jc2 = job_control
618
+ j2 = slow.exec!
619
+ }
620
+
621
+ assert_not_equal jc1, jc2
622
+
623
+ assert_equal [j1], jc1.jobs
624
+ assert_equal [j2], jc2.jobs
625
+
626
+ t = Helper.time_elapsed {
627
+ jc1.waitall
628
+ jc2.waitall
629
+ }
630
+
631
+ assert_in_delta 1, t, 0.1
632
+ assert jc1.jobs.empty?
633
+ assert jc2.jobs.empty?
634
+ }
635
+ end
636
+
637
+ end
638
+
639
+ class Rubish::Test::Job < TUT
640
+
641
+ def setup
642
+ setup_tmp
643
+ end
644
+
645
+ should "belong to job_control" do
646
+ rsh {
647
+ jc1 = job_control
648
+ j1 = ls.exec!
649
+ j2, jc2 = nil
650
+ with {
651
+ jc2 = job_control
652
+ }
653
+
654
+ assert_equal jc1, j1.job_control
655
+ assert_not_equal jc2, j1.job_control
656
+
657
+ assert_raise(Rubish::Error) {
658
+ jc2.remove(j1)
659
+ }
660
+
661
+ }
662
+ end
663
+
664
+ should "set result to array of exit statuses" do
665
+ rsh {
666
+ ls.exec.each { |status|
667
+ assert_instance_of Process::Status, status
668
+ assert_equal 0, status.exitstatus
669
+ }
670
+ }
671
+ end
672
+
673
+ should "map in parrallel to different array" do
674
+ slow = Helper.slowcat(1)
675
+ a1, a2, a3 = [[],[],[]]
676
+ j1 = slow.map! a1
677
+ j2 = slow.map! a2
678
+ j3 = slow.map! a3
679
+ js = [j1,j2,j3]
680
+ t = Helper.time_elapsed {
681
+ js.each { |j| j.wait }
682
+ }
683
+ assert_in_delta 1, t, 0.1
684
+ assert j1.done? && j2.done? && j3.done?
685
+ rs = [a1,a2,a3]
686
+ # each result should be an array of sized 3
687
+ assert(rs.all? { |r| r.size == 1 })
688
+ # should be accumulated into different arrays
689
+ assert_equal(3,rs.map{|r| r.object_id }.uniq.size)
690
+ end
691
+
692
+ should "map in parrallel to thread safe queue" do
693
+ slow = Helper.slowcat(1)
694
+ acc = Queue.new
695
+ j1 = slow.map! acc
696
+ j2 = slow.map! acc
697
+ j3 = slow.map! acc
698
+ js = [j1,j2,j3]
699
+ t = Helper.time_elapsed {
700
+ j1.wait; j2.wait; j3.wait
701
+ }
702
+ assert_in_delta 1, t, 0.1
703
+ assert j1.done? && j2.done? && j3.done?
704
+ # each result should be an array of sized 3
705
+ assert_equal 3, acc.size
706
+ end
707
+
708
+ should "wait for job" do
709
+ job = Helper.slowcat(1).exec!
710
+ assert_equal false, job.done?
711
+ t = Helper.time_elapsed { job.wait }
712
+ assert_in_delta 0.1, t, 1
713
+ assert_equal true, job.done?
714
+ end
715
+
716
+ should "raise when waited twice" do
717
+ assert_raise(Rubish::Error) {
718
+ rsh {
719
+ j = ls.exec!
720
+ j.wait
721
+ j.wait
722
+ }
723
+ }
724
+ end
725
+
726
+ should "kill a job" do
727
+ acc = []
728
+ j = Helper.slowcat(10).map!(acc)
729
+ e = nil
730
+ t = Helper.time_elapsed {
731
+ sleep(2)
732
+ begin
733
+ j.stop!
734
+ rescue
735
+ e = $!
736
+ assert_instance_of Rubish::Job::Failure, e
737
+ assert_equal j, e.job
738
+ assert_equal 1, j.bads.size
739
+ assert_equal 0, j.goods.size
740
+ end
741
+ }
742
+ assert_in_delta 2, acc.size, 1, "expects to get roughly two lines out before killing process"
743
+ assert_in_delta 2, t, 0.1
744
+ assert j.done?
745
+
746
+
747
+
748
+ end
749
+
750
+ end
751
+
752
+
753
+ class Rubish::Test::JobControl < TUT
754
+
755
+ should "use job control" do
756
+ rsh {
757
+ slow = Helper.slowcat(1).o "/dev/null"
758
+ job1 = slow.exec!
759
+ job2 = slow.exec!
760
+ assert_kind_of Rubish::Job, job1
761
+ assert_kind_of Rubish::Job, job2
762
+ assert_equal 2, jobs.size
763
+ assert_instance_of Array, jobs
764
+ assert jobs.include?(job1)
765
+ assert jobs.include?(job2)
766
+ job1.wait
767
+ job2.wait
768
+ assert jobs.empty?, "expects jobs to empty"
769
+ }
770
+ end
771
+
772
+ should "job control waitall" do
773
+ rsh {
774
+ puts "slowcat 1 * 3 lines in sequence"
775
+ slow = Helper.slowcat(1)
776
+ cats = (1..3).to_a.map { slow.exec! }
777
+ assert_equal 3, jobs.size
778
+ assert cats.all? { |cat| jobs.include?(cat) }
779
+ t = Helper.time_elapsed { waitall }
780
+ assert_in_delta 1, t, 0.1
781
+ assert jobs.empty?
782
+ }
783
+ end
784
+
785
+ end
786
+
787
+
788
+ class Rubish::Test::Batch < TUT
789
+
790
+ def setup
791
+ setup_tmp
792
+ end
793
+
794
+ should "raise exception" do
795
+ rsh {
796
+ b = batch {
797
+ 1/0
798
+ }
799
+ j = nil
800
+ assert_raise(Rubish::Job::Failure) {
801
+ b.exec
802
+ }
803
+ assert jobs.empty?
804
+
805
+ begin
806
+ b.exec
807
+ rescue Rubish::Job::Failure => e
808
+ assert_instance_of ZeroDivisionError, e.reason
809
+ assert_kind_of Rubish::Job, e.job
810
+ j = e.job
811
+ assert j.done?
812
+ assert_nil j.result
813
+ assert jobs.empty?
814
+ end
815
+
816
+ assert_raise(Rubish::Job::Failure) {
817
+ j = b.exec!
818
+ j.wait
819
+ }
820
+ assert j.done?
821
+ assert_nil j.result
822
+ assert jobs.empty?
823
+ }
824
+ end
825
+
826
+
827
+ should "do batch as job" do
828
+ rsh {
829
+ b = batch {
830
+ cat.i { |p| p.puts((1..10).to_a) }.exec
831
+ cat.i { |p| p.puts((11..20).to_a) }.exec
832
+ :result
833
+ }
834
+
835
+ rs = b.map { |i| i.to_i }
836
+ assert jobs.empty?
837
+ assert_equal (1..20).to_a, rs
838
+
839
+ j1 = b.exec!
840
+ assert_equal [j1], jobs
841
+ j1.wait
842
+ assert j1.done?
843
+ assert_equal :result, j1.result
844
+ assert jobs.empty?
845
+ }
846
+
847
+ end
848
+
849
+
850
+ should "use context's IOs to execute in a batch" do
851
+ rsh {
852
+ b = batch {
853
+ # use the contextual stdioe
854
+ cat.i { |p| p.puts((1..10).to_a) }.exec
855
+ # fix the output to bo2, only for this executable
856
+ cat.i { |p| p.puts((100..110).to_a) }.o("bo2").exec
857
+ }.o("bo1")
858
+
859
+ b.exec
860
+ assert jobs.empty?
861
+ assert_equal (1..10).to_a, cat.i("bo1").map { |i| i.to_i }
862
+ assert_equal (100..110).to_a, cat.i("bo2").map { |i| i.to_i }
863
+
864
+ rm("*").exec
865
+ b.o("bo3").exec
866
+ assert jobs.empty?
867
+ assert !File.exist?("bo1")
868
+ assert_equal (1..10).to_a, cat.i("bo3").map { |i| i.to_i }
869
+ assert_equal (100..110).to_a, cat.i("bo2").map { |i| i.to_i }
870
+
871
+ }
872
+
873
+ end
874
+
875
+ should "be concurrent" do
876
+ rsh {
877
+ slow = Helper.slowcat(1)
878
+ b = batch {
879
+ slow.exec!
880
+ slow.exec!
881
+ }
882
+
883
+ t = Helper.time_elapsed { b.exec }
884
+ assert_in_delta 1, t, 0.2
885
+ assert jobs.empty?
886
+
887
+ j1, j2 = b.exec!, b.exec!
888
+ assert_equal 2, jobs.size
889
+ t = Helper.time_elapsed { waitall }
890
+ assert j1.done?
891
+ assert j2.done?
892
+ assert jobs.empty?
893
+ }
894
+ end
895
+
896
+ end