deadly_serious 1.0.2 → 2.0.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/Guardfile +2 -2
  3. data/deadly_serious.gemspec +1 -0
  4. data/lib/deadly_serious.rb +7 -6
  5. data/lib/deadly_serious/engine/auto_pipe.rb +33 -21
  6. data/lib/deadly_serious/engine/channel.rb +15 -130
  7. data/lib/deadly_serious/engine/channel/file_channel.rb +50 -0
  8. data/lib/deadly_serious/engine/channel/pipe_channel.rb +59 -0
  9. data/lib/deadly_serious/engine/channel/socket/master_mind.rb +42 -0
  10. data/lib/deadly_serious/engine/channel/socket/minion.rb +24 -0
  11. data/lib/deadly_serious/engine/channel/socket/socket_sink_recvr.rb +34 -0
  12. data/lib/deadly_serious/engine/channel/socket/socket_sink_sendr.rb +26 -0
  13. data/lib/deadly_serious/engine/channel/socket/socket_vent_recvr.rb +32 -0
  14. data/lib/deadly_serious/engine/channel/socket/socket_vent_sendr.rb +30 -0
  15. data/lib/deadly_serious/engine/channel/socket_channel.rb +75 -0
  16. data/lib/deadly_serious/engine/commands.rb +29 -8
  17. data/lib/deadly_serious/engine/config.rb +55 -0
  18. data/lib/deadly_serious/engine/file_monitor.rb +57 -0
  19. data/lib/deadly_serious/engine/json_io.rb +13 -11
  20. data/lib/deadly_serious/engine/pipeline.rb +31 -87
  21. data/lib/deadly_serious/engine/ruby_object_container.rb +42 -0
  22. data/lib/deadly_serious/engine/so_command_container.rb +56 -0
  23. data/lib/deadly_serious/processes/converter.rb +12 -0
  24. data/lib/deadly_serious/processes/lambda.rb +4 -2
  25. data/lib/deadly_serious/processes/resilient_splitter.rb +1 -1
  26. data/lib/deadly_serious/version.rb +1 -1
  27. data/spec/lib/deadly_serious/engine/auto_pipe_spec.rb +41 -0
  28. data/spec/lib/deadly_serious/engine/channel/socket_channel_spec.rb +159 -0
  29. data/spec/{deadly_serious → lib/deadly_serious}/engine/commands_spec.rb +0 -0
  30. data/spec/lib/deadly_serious/engine/file_monitor_spec.rb +69 -0
  31. data/spec/{deadly_serious → lib/deadly_serious}/engine/json_io_spec.rb +0 -0
  32. data/spec/{deadly_serious → lib/deadly_serious}/engine/pipeline_spec.rb +37 -40
  33. data/spec/spec_helper.rb +4 -1
  34. metadata +51 -14
  35. data/lib/deadly_serious/engine/lazy_io.rb +0 -82
  36. data/lib/deadly_serious/engine/open_io.rb +0 -39
  37. data/lib/deadly_serious/processes/joiner.rb +0 -15
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+ include DeadlySerious::Engine
3
+
4
+ describe FileMonitor do
5
+ let(:test_file) { '/tmp/deadly_serious_test/test_file' }
6
+ let(:test_file2) { '/tmp/deadly_serious_test/test_file2' }
7
+
8
+ before do
9
+ FileUtils.makedirs('/tmp/deadly_serious_test')
10
+ end
11
+
12
+ after do
13
+ FileUtils.rm_rf('/tmp/deadly_serious_test')
14
+ end
15
+
16
+ subject { FileMonitor.new(test_file) }
17
+
18
+ it 'blocks until file created' do
19
+ t = Thread.new { subject.wait_creation }
20
+
21
+ sleep 0.1
22
+ expect(t.alive?).to be_truthy
23
+
24
+ `touch #{test_file}`
25
+
26
+ sleep 0.1
27
+ expect(t.alive?).to be_falsey
28
+
29
+ t.join(1)
30
+ end
31
+
32
+ it "don't block if file already exists" do
33
+ `touch #{test_file}`
34
+ t = Thread.new { subject.wait_creation }
35
+
36
+ sleep 0.1
37
+ expect(t.alive?).to be_falsey
38
+
39
+ t.join(1)
40
+ end
41
+
42
+ it 'blocks until file change' do
43
+ `touch #{test_file}`
44
+ t = Thread.new { subject.wait_modification }
45
+
46
+ sleep 0.1
47
+ expect(t.alive?).to be_truthy
48
+
49
+ File.write(test_file, "test\n")
50
+
51
+ sleep 0.1
52
+ expect(t.alive?).to be_falsey
53
+
54
+ t.join(1)
55
+ end
56
+
57
+ it 'returns modified file' do
58
+ `touch #{test_file}`
59
+ `touch #{test_file2}`
60
+ t = Thread.new { FileMonitor.new(test_file, test_file2).wait_modification }
61
+
62
+ sleep 0.1
63
+ expect(t.alive?).to be_truthy
64
+
65
+ File.write(test_file2, "test\n")
66
+ t.join(1)
67
+ expect(t.value).to eq test_file2
68
+ end
69
+ end
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'fileutils'
3
3
  include DeadlySerious::Engine
4
+ include DeadlySerious::Processes
4
5
 
5
6
  describe Pipeline do
6
7
  DELAY = 0.5 # seconds
@@ -13,15 +14,23 @@ describe Pipeline do
13
14
  end
14
15
  end
15
16
 
16
- class TestComponentMultiplyBy2
17
+ class TestComponentMultiplier
18
+ def initialize(multi)
19
+ @multi = multi
20
+ end
21
+
17
22
  def run(readers:, writers:)
18
23
  reader = JsonIo.new(readers.first)
19
24
  writer = JsonIo.new(writers.first)
20
25
 
21
26
  reader.each do |(number)|
22
- writer << [number * 2]
27
+ writer << [number * @multi]
23
28
  end
24
29
  end
30
+
31
+ def inspect
32
+ format '%s[%d]', self.class.name, Process.pid
33
+ end
25
34
  end
26
35
 
27
36
  before do
@@ -63,37 +72,6 @@ describe Pipeline do
63
72
  expect(finish - start).to be >= DELAY
64
73
  end
65
74
 
66
- it 'kills all children on SIGTERM' do
67
- pending 'unpredictable'
68
- start = Time.now
69
-
70
- reader, writer = IO.pipe
71
- pipeline_id = fork do
72
- reader.close
73
- pipeline = Pipeline.new do |p|
74
- p.spawn_process(TestComponentTime, DELAY)
75
- p.spawn_process(TestComponentTime, DELAY)
76
- p.spawn_process(TestComponentTime, DELAY)
77
- p.spawn_process(TestComponentTime, DELAY)
78
- writer << p.pids
79
- writer.close
80
- end
81
- pipeline.run
82
- end
83
- writer.close
84
- c1, c2, c3, c4 = eval(reader.readlines.first)
85
- reader.close
86
- expect(c1).to be_running
87
- expect(c2).to be_running
88
- expect(c3).to be_running
89
- expect(c4).to be_running
90
- Process.kill('SIGTERM', pipeline_id)
91
- Process.wait(pipeline_id)
92
- finish = Time.now
93
- expect { Process.wait2(pipeline_id, Process::WNOHANG) }.to raise_error Errno::ECHILD
94
- expect(finish - start).to be < (DELAY / 4)
95
- end
96
-
97
75
  it 'spawns children in parallel' do
98
76
  pipeline = Pipeline.new do |p|
99
77
  p.spawn_process(TestComponentTime, DELAY)
@@ -111,29 +89,30 @@ describe Pipeline do
111
89
  end
112
90
 
113
91
  it 'spawns linux commands' do
92
+ open(test_file, 'w') { |f| f.puts('line 1'); f.puts('line 2')}
114
93
  pipeline = Pipeline.new do |p|
115
94
  p.from_file(test_file)
116
95
  p.spawn_command('cat')
117
96
  p.to_file(result_file)
118
97
  end
119
- expect(test_file).to_not exists
120
98
  pipeline.run
121
- expect(test_file).to exists
99
+ expect(result_file).to exists
100
+ expect(File.readlines(result_file)).to eq ["line 1\n", "line 2\n"]
122
101
  end
123
102
 
124
103
  it 'connects pipes with Component Classes' do
125
- create_file(test_file, [[1], [2], [3]])
104
+ create_file(test_file, [[1], [3], [5]])
126
105
  pipeline = Pipeline.new do |p|
127
106
  p.from_file(test_file)
128
- p.spawn_process(TestComponentMultiplyBy2)
129
- p.spawn_process(TestComponentMultiplyBy2)
107
+ p.spawn_process(TestComponentMultiplier.new(2))
108
+ p.spawn_process(TestComponentMultiplier.new(2))
130
109
  p.to_file(result_file)
131
110
  end
132
111
  pipeline.run
133
- expect(result_file).to have_content [[4], [8], [12]]
112
+ expect(result_file).to have_content [[4], [12], [20]]
134
113
  end
135
114
 
136
- it 'connects pipes with shell commands' do
115
+ it 'connects pipes with shell commands'do
137
116
  create_file(test_file, [[1], [2], [3]])
138
117
  pipeline = Pipeline.new do |p|
139
118
  p.from_file(test_file)
@@ -144,4 +123,22 @@ describe Pipeline do
144
123
  pipeline.run
145
124
  expect(result_file).to have_content [['_1_'], ['_2_'], ['_3_']]
146
125
  end
126
+
127
+ it 'parallelize things' do
128
+ create_file(test_file, (1..10_000).map { |i| [i] })
129
+ pipeline = Pipeline.new do |p|
130
+ p.from_file(test_file)
131
+ p.spawn_process(Converter.new, writers: ['>{localhost:5555'])
132
+ p.spawn_process(TestComponentMultiplier.new(10), readers: ['<{localhost:5555'], writers: ['>}localhost:5556'])
133
+ p.spawn_process(TestComponentMultiplier.new(100), readers: ['<{localhost:5555'], writers: ['>}localhost:5556'])
134
+ p.spawn_process(TestComponentMultiplier.new(1000), readers: ['<{localhost:5555'], writers: ['>}localhost:5556'])
135
+ p.spawn_process(Converter.new, readers: ['<}localhost:5556'])
136
+ p.to_file(result_file)
137
+ end
138
+ pipeline.run
139
+ lines = IO.readlines(result_file)
140
+ expect(lines.size).to eq 10_000
141
+ expect(lines).to include("[10]\n") | include("[100]\n") | include("[1000]\n")
142
+ expect(lines).to include("[100000]\n") | include("[1000000]\n") | include("[10000000]\n")
143
+ end
147
144
  end
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  require 'deadly_serious'
2
2
  require 'fileutils'
3
-
4
3
  RSpec.configure do |config|
4
+
5
+ config.filter_run :focus => true
6
+ config.run_all_when_everything_filtered = true
7
+
5
8
  config.before(:each) do
6
9
  FileUtils.mkdir_p '/tmp/deadly_serious/'
7
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deadly_serious
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.0.0.pre.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ronie Uliana
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-20 00:00:00.000000000 Z
11
+ date: 2015-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,26 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '1.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rbczmq
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.7'
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 1.7.9
107
+ type: :runtime
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '1.7'
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 1.7.9
97
117
  description: Flow Based Programming Maestro
98
118
  email:
99
119
  - ronie.uliana@gmail.com
@@ -113,19 +133,33 @@ files:
113
133
  - lib/deadly_serious.rb
114
134
  - lib/deadly_serious/engine/auto_pipe.rb
115
135
  - lib/deadly_serious/engine/channel.rb
136
+ - lib/deadly_serious/engine/channel/file_channel.rb
137
+ - lib/deadly_serious/engine/channel/pipe_channel.rb
138
+ - lib/deadly_serious/engine/channel/socket/master_mind.rb
139
+ - lib/deadly_serious/engine/channel/socket/minion.rb
140
+ - lib/deadly_serious/engine/channel/socket/socket_sink_recvr.rb
141
+ - lib/deadly_serious/engine/channel/socket/socket_sink_sendr.rb
142
+ - lib/deadly_serious/engine/channel/socket/socket_vent_recvr.rb
143
+ - lib/deadly_serious/engine/channel/socket/socket_vent_sendr.rb
144
+ - lib/deadly_serious/engine/channel/socket_channel.rb
116
145
  - lib/deadly_serious/engine/commands.rb
146
+ - lib/deadly_serious/engine/config.rb
147
+ - lib/deadly_serious/engine/file_monitor.rb
117
148
  - lib/deadly_serious/engine/json_io.rb
118
- - lib/deadly_serious/engine/lazy_io.rb
119
- - lib/deadly_serious/engine/open_io.rb
120
149
  - lib/deadly_serious/engine/pipeline.rb
121
- - lib/deadly_serious/processes/joiner.rb
150
+ - lib/deadly_serious/engine/ruby_object_container.rb
151
+ - lib/deadly_serious/engine/so_command_container.rb
152
+ - lib/deadly_serious/processes/converter.rb
122
153
  - lib/deadly_serious/processes/lambda.rb
123
154
  - lib/deadly_serious/processes/resilient_splitter.rb
124
155
  - lib/deadly_serious/processes/splitter.rb
125
156
  - lib/deadly_serious/version.rb
126
- - spec/deadly_serious/engine/commands_spec.rb
127
- - spec/deadly_serious/engine/json_io_spec.rb
128
- - spec/deadly_serious/engine/pipeline_spec.rb
157
+ - spec/lib/deadly_serious/engine/auto_pipe_spec.rb
158
+ - spec/lib/deadly_serious/engine/channel/socket_channel_spec.rb
159
+ - spec/lib/deadly_serious/engine/commands_spec.rb
160
+ - spec/lib/deadly_serious/engine/file_monitor_spec.rb
161
+ - spec/lib/deadly_serious/engine/json_io_spec.rb
162
+ - spec/lib/deadly_serious/engine/pipeline_spec.rb
129
163
  - spec/spec_helper.rb
130
164
  homepage: https://github.com/ruliana/deadly_serious
131
165
  licenses:
@@ -142,19 +176,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
176
  version: '2.1'
143
177
  required_rubygems_version: !ruby/object:Gem::Requirement
144
178
  requirements:
145
- - - ">="
179
+ - - ">"
146
180
  - !ruby/object:Gem::Version
147
- version: '0'
181
+ version: 1.3.1
148
182
  requirements: []
149
183
  rubyforge_project:
150
- rubygems_version: 2.4.6
184
+ rubygems_version: 2.4.8
151
185
  signing_key:
152
186
  specification_version: 4
153
187
  summary: Flow Based Programming maestro that relies on named pipes and Linux processes
154
188
  (sorry, no Windows here). That means it uses 'mechanical sympathy' with the Operating
155
189
  System, i.e., the S.O. is *part* of the program, it's not something *under* it.
156
190
  test_files:
157
- - spec/deadly_serious/engine/commands_spec.rb
158
- - spec/deadly_serious/engine/json_io_spec.rb
159
- - spec/deadly_serious/engine/pipeline_spec.rb
191
+ - spec/lib/deadly_serious/engine/auto_pipe_spec.rb
192
+ - spec/lib/deadly_serious/engine/channel/socket_channel_spec.rb
193
+ - spec/lib/deadly_serious/engine/commands_spec.rb
194
+ - spec/lib/deadly_serious/engine/file_monitor_spec.rb
195
+ - spec/lib/deadly_serious/engine/json_io_spec.rb
196
+ - spec/lib/deadly_serious/engine/pipeline_spec.rb
160
197
  - spec/spec_helper.rb
@@ -1,82 +0,0 @@
1
- module DeadlySerious
2
- module Engine
3
-
4
- # Restrict IO class that opens ONLY
5
- # when trying to read something.
6
- #
7
- # *This is the object passed to Components.*
8
- #
9
- # Also, used to reopen lost connections.
10
- #
11
- # By "restrict", I mean it implements
12
- # just a few IO operations.
13
- class LazyIo
14
- def initialize(channel)
15
- @channel = channel
16
- end
17
-
18
- # @return [String, nil] the name of the file or pipe,
19
- # nil if it's a socket
20
- def filename
21
- @channel.io_name if @channel.respond_to?(:io_name)
22
- end
23
-
24
- def gets
25
- open_reader
26
- @io.gets
27
- end
28
-
29
- def each(&block)
30
- open_reader
31
- @io.each &block
32
- end
33
-
34
- def each_cons(qty, &block)
35
- open_reader
36
- @io.each_cons(qty, &block)
37
- end
38
-
39
- def each_with_object(object, &block)
40
- open_reader
41
- @io.each_with_object(object, &block)
42
- end
43
-
44
- def <<(element)
45
- open_writer
46
- @io << element
47
- end
48
-
49
- def eof?
50
- open_reader
51
- @io.eof?
52
- end
53
-
54
- def closed?
55
- @io.nil? || @io.closed?
56
- end
57
-
58
- def close
59
- @io.close unless closed?
60
- @io = nil
61
- end
62
-
63
- def flush
64
- @io.flush unless closed?
65
- end
66
-
67
- private
68
-
69
- def open_reader
70
- if closed?
71
- @io = @channel.open_reader
72
- end
73
- end
74
-
75
- def open_writer
76
- if closed?
77
- @io = @channel.open_writer
78
- end
79
- end
80
- end
81
- end
82
- end
@@ -1,39 +0,0 @@
1
- require 'fileutils'
2
-
3
- module DeadlySerious
4
- module Engine
5
- # Wraps the channels with a {LazyIO} before pass
6
- # them to the component.
7
- module OpenIo
8
- def run(*args, readers: [], writers:[])
9
- opened_readers = readers.map { |reader| wrap_io(reader) }
10
- opened_writers = writers.map { |writer| wrap_io(writer) }
11
- super(*args, readers: opened_readers, writers: opened_writers)
12
- ensure
13
- if opened_writers
14
- opened_writers.each { |writer| close_io(writer) }
15
- end
16
- if opened_readers
17
- opened_readers.each { |reader| close_io(reader) }
18
- end
19
- end
20
-
21
- private
22
-
23
- def close_io(io)
24
- return unless io
25
- return if io.closed?
26
- io.close
27
- rescue => e
28
- # Intentionally eat the error
29
- # because it's being used inside
30
- # an "ensure" block
31
- puts e.inspect
32
- end
33
-
34
- def wrap_io(pipe_name)
35
- Channel.new(pipe_name).io
36
- end
37
- end
38
- end
39
- end