rubish 0.0.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,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