standard-procedure-plumbing 0.3.3 → 0.4.0

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.
@@ -1,20 +1,21 @@
1
1
  require "spec_helper"
2
2
 
3
- RSpec.shared_examples "an example valve" do |runs_in_background|
3
+ RSpec.shared_examples "an example actor" do |runs_in_background|
4
4
  it "queries an object" do
5
5
  @person = Employee.start "Alice"
6
6
 
7
- expect(@person.name).to eq "Alice"
8
- expect(@person.job_title).to eq "Sales assistant"
7
+ expect(await { @person.name }).to eq "Alice"
8
+ expect(await { @person.job_title }).to eq "Sales assistant"
9
9
 
10
10
  @time = Time.now
11
11
  # `greet_slowly` is a query so will block until a response is received
12
- expect(@person.greet_slowly).to eq "H E L L O"
12
+ expect(await { @person.greet_slowly }).to eq "H E L L O"
13
13
  expect(Time.now - @time).to be > 0.1
14
14
 
15
15
  @time = Time.now
16
- # we're ignoring the result so this will not block (except :inline mode which does not run in the background)
17
- expect(@person.greet_slowly(ignore_result: true)).to be_nil
16
+ # we're not awaiting the result, so this should run in the background (unless we're using inline mode)
17
+ @person.greet_slowly
18
+
18
19
  expect(Time.now - @time).to be < 0.1 if runs_in_background
19
20
  expect(Time.now - @time).to be > 0.1 if !runs_in_background
20
21
  end
@@ -22,17 +23,16 @@ RSpec.shared_examples "an example valve" do |runs_in_background|
22
23
  it "commands an object" do
23
24
  @person = Employee.start "Alice"
24
25
  @person.promote
25
- @job_title = @person.job_title
26
+ @job_title = await { @person.job_title }
26
27
  expect(@job_title).to eq "Sales manager"
27
28
  end
28
29
  end
29
30
 
30
- RSpec.describe "Valve example: " do
31
+ RSpec.describe "Actor example: " do
31
32
  # standard:disable Lint/ConstantDefinitionInBlock
32
33
  class Employee
33
- include Plumbing::Valve
34
- query :name, :job_title, :greet_slowly
35
- command :promote
34
+ include Plumbing::Actor
35
+ async :name, :job_title, :greet_slowly, :promote
36
36
 
37
37
  def initialize(name)
38
38
  @name = name
@@ -58,7 +58,7 @@ RSpec.describe "Valve example: " do
58
58
  Plumbing.configure mode: :inline, &example
59
59
  end
60
60
 
61
- it_behaves_like "an example valve", false
61
+ it_behaves_like "an example actor", false
62
62
  end
63
63
 
64
64
  context "async mode" do
@@ -68,7 +68,7 @@ RSpec.describe "Valve example: " do
68
68
  end
69
69
  end
70
70
 
71
- it_behaves_like "an example valve", true
71
+ it_behaves_like "an example actor", true
72
72
  end
73
73
 
74
74
  context "threaded mode" do
@@ -76,6 +76,6 @@ RSpec.describe "Valve example: " do
76
76
  Plumbing.configure mode: :threaded, &example
77
77
  end
78
78
 
79
- it_behaves_like "an example valve", true
79
+ it_behaves_like "an example actor", true
80
80
  end
81
81
  end
@@ -6,7 +6,7 @@ RSpec.describe "Pipe examples" do
6
6
  @source = Plumbing::Pipe.start
7
7
 
8
8
  @result = []
9
- @observer = @source.add_observer do |event|
9
+ @source.add_observer do |event|
10
10
  @result << event.type
11
11
  end
12
12
 
@@ -22,7 +22,7 @@ RSpec.describe "Pipe examples" do
22
22
  end
23
23
 
24
24
  @result = []
25
- @observer = @filter.add_observer do |event|
25
+ @filter.add_observer do |event|
26
26
  @result << event.type
27
27
  end
28
28
 
@@ -55,7 +55,7 @@ RSpec.describe "Pipe examples" do
55
55
  @filter = EveryThirdEvent.new(source: @source)
56
56
 
57
57
  @result = []
58
- @observer = @filter.add_observer do |event|
58
+ @filter.add_observer do |event|
59
59
  @result << event.type
60
60
  end
61
61
 
@@ -73,7 +73,7 @@ RSpec.describe "Pipe examples" do
73
73
  @junction = Plumbing::Junction.start @first_source, @second_source
74
74
 
75
75
  @result = []
76
- @observer = @junction.add_observer do |event|
76
+ @junction.add_observer do |event|
77
77
  @result << event.type
78
78
  end
79
79
 
@@ -1,10 +1,12 @@
1
1
  RSpec.shared_examples "a pipe" do
2
2
  it "adds a block observer" do
3
3
  @pipe = described_class.start
4
- @observer = @pipe.add_observer do |event|
5
- puts event.type
4
+ @observer = await do
5
+ @pipe.add_observer do |event|
6
+ puts event.type
7
+ end
6
8
  end
7
- expect(@pipe.is_observer?(@observer)).to eq true
9
+ expect(await { @pipe.is_observer?(@observer) }).to eq true
8
10
  end
9
11
 
10
12
  it "adds a callable observer" do
@@ -13,13 +15,13 @@ RSpec.shared_examples "a pipe" do
13
15
 
14
16
  @pipe.add_observer @proc
15
17
 
16
- expect(@pipe.is_observer?(@proc)).to eq true
18
+ expect(await { @pipe.is_observer?(@proc) }).to eq true
17
19
  end
18
20
 
19
21
  it "does not allow an observer without a #call method" do
20
22
  @pipe = described_class.start
21
23
 
22
- expect { @pipe.add_observer(Object.new) }.to raise_error(TypeError)
24
+ expect { await { @pipe.add_observer(Object.new) } }.to raise_error(TypeError)
23
25
  end
24
26
 
25
27
  it "removes an observer" do
@@ -28,10 +30,10 @@ RSpec.shared_examples "a pipe" do
28
30
 
29
31
  @pipe.remove_observer @proc
30
32
 
31
- expect(@pipe.is_observer?(@proc)).to eq false
33
+ expect(await { @pipe.is_observer?(@proc) }).to eq false
32
34
  end
33
35
 
34
- it "does not send notifications for objects which are not events" do
36
+ it "does not send notifications for objects which are not events" do
35
37
  @pipe = described_class.start
36
38
  @results = []
37
39
  @observer = @pipe.add_observer do |event|
@@ -40,7 +42,7 @@ RSpec.shared_examples "a pipe" do
40
42
 
41
43
  @pipe << Object.new
42
44
 
43
- sleep 0.5
45
+ sleep 0.1
44
46
  expect(@results).to eq []
45
47
  end
46
48
 
@@ -101,6 +103,6 @@ RSpec.shared_examples "a pipe" do
101
103
 
102
104
  @pipe.shutdown
103
105
 
104
- expect(@pipe.is_observer?(@observer)).to eq false
106
+ expect(await { @pipe.is_observer?(@observer) }).to eq false
105
107
  end
106
108
  end
@@ -0,0 +1,158 @@
1
+ require "spec_helper"
2
+ require_relative "../../../lib/plumbing/actor/transporter"
3
+
4
+ RSpec.describe Plumbing::Actor::Transporter do
5
+ # standard:disable Lint/ConstantDefinitionInBlock
6
+ class Record
7
+ include GlobalID::Identification
8
+ attr_reader :id
9
+ def initialize id
10
+ @id = id
11
+ end
12
+
13
+ def == other
14
+ other.id == @id
15
+ end
16
+ end
17
+ # standard:enable Lint/ConstantDefinitionInBlock
18
+ before do
19
+ GlobalID.app = "rspec"
20
+ GlobalID::Locator.use :rspec do |gid, options|
21
+ Record.new gid.model_id
22
+ end
23
+ end
24
+
25
+ context "marshalling" do
26
+ it "passes simple arguments" do
27
+ @transporter = described_class.new
28
+
29
+ @transport = @transporter.marshal "Hello"
30
+ expect(@transport).to eq ["Hello"]
31
+
32
+ @transport = @transporter.marshal 1, 2, 3
33
+ expect(@transport).to eq [1, 2, 3]
34
+ end
35
+
36
+ it "copies arrays" do
37
+ @transporter = described_class.new
38
+
39
+ @source = [[1, 2, 3], [:this, :that]]
40
+
41
+ @transport = @transporter.marshal(*@source)
42
+ expect(@transport).to eq @source
43
+ expect(@transport.first.object_id).to_not eq @source.first.object_id
44
+ expect(@transport.last.object_id).to_not eq @source.last.object_id
45
+ end
46
+
47
+ it "copies hashss" do
48
+ @transporter = described_class.new
49
+
50
+ @source = [{first: "1", second: 2}]
51
+
52
+ @transport = @transporter.marshal(*@source)
53
+ expect(@transport).to eq @source
54
+ expect(@transport.first.object_id).to_not eq @source.first.object_id
55
+ end
56
+
57
+ it "converts objects to Global ID strings" do
58
+ @transporter = described_class.new
59
+
60
+ @record = Record.new 123
61
+ @global_id = @record.to_global_id.to_s
62
+
63
+ @transport = @transporter.marshal @record
64
+
65
+ expect(@transport).to eq [@global_id]
66
+ end
67
+
68
+ it "converts objects within arrays to Global ID strings" do
69
+ @transporter = described_class.new
70
+
71
+ @record = Record.new 123
72
+ @global_id = @record.to_global_id.to_s
73
+
74
+ @transport = @transporter.marshal [:this, @record]
75
+
76
+ expect(@transport).to eq [[:this, @global_id]]
77
+ end
78
+
79
+ it "converts objects within hashes to Global ID strings" do
80
+ @transporter = described_class.new
81
+
82
+ @record = Record.new 123
83
+ @global_id = @record.to_global_id.to_s
84
+
85
+ @transport = @transporter.marshal this: "that", the_other: {embedded: @record}
86
+
87
+ expect(@transport).to eq [{this: "that", the_other: {embedded: @global_id}}]
88
+ end
89
+ end
90
+
91
+ context "unmarshalling" do
92
+ it "passes simple arguments" do
93
+ @transporter = described_class.new
94
+
95
+ @transport = @transporter.unmarshal "Hello"
96
+ expect(@transport).to eq ["Hello"]
97
+
98
+ @transport = @transporter.unmarshal 1, 2, 3
99
+ expect(@transport).to eq [1, 2, 3]
100
+ end
101
+
102
+ it "passes arrays" do
103
+ @transporter = described_class.new
104
+
105
+ @transport = @transporter.unmarshal [1, 2, 3], [:this, :that]
106
+
107
+ expect(@transport.first.object_id).to_not eq [1, 2, 3]
108
+ expect(@transport.last.object_id).to_not eq [:this, :that]
109
+ end
110
+
111
+ it "passes hashss and keyword arguments" do
112
+ @transporter = described_class.new
113
+
114
+ @transport = @transporter.unmarshal first: "1", second: 2
115
+ expect(@transport).to eq [{first: "1", second: 2}]
116
+ end
117
+
118
+ it "passes mixtures of arrays and hashes" do
119
+ @transporter = described_class.new
120
+
121
+ @transport = @transporter.unmarshal :this, :that, first: "1", second: 2
122
+ expect(@transport).to eq [:this, :that, {first: "1", second: 2}]
123
+ end
124
+
125
+ it "converts Global ID strings to objects" do
126
+ @transporter = described_class.new
127
+
128
+ @record = Record.new "123"
129
+ @global_id = @record.to_global_id.to_s
130
+
131
+ @transport = @transporter.unmarshal @global_id
132
+
133
+ expect(@transport).to eq [@record]
134
+ end
135
+
136
+ it "converts Global ID strings within arrays to objects" do
137
+ @transporter = described_class.new
138
+
139
+ @record = Record.new "123"
140
+ @global_id = @record.to_global_id.to_s
141
+
142
+ @transport = @transporter.unmarshal :this, @global_id
143
+
144
+ expect(@transport).to eq [:this, @record]
145
+ end
146
+
147
+ it "converts Global ID strings within hashes to objects" do
148
+ @transporter = described_class.new
149
+
150
+ @record = Record.new "123"
151
+ @global_id = @record.to_global_id.to_s
152
+
153
+ @transport = @transporter.unmarshal this: "that", the_other: {embedded: @global_id}
154
+
155
+ expect(@transport).to eq [{this: "that", the_other: {embedded: @record}}]
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,208 @@
1
+ require "spec_helper"
2
+
3
+ require_relative "../../lib/plumbing/actor/async"
4
+ require_relative "../../lib/plumbing/actor/threaded"
5
+ require_relative "../../lib/plumbing/actor/rails"
6
+
7
+ RSpec.describe Plumbing::Actor do
8
+ # standard:disable Lint/ConstantDefinitionInBlock
9
+ class Counter
10
+ include Plumbing::Actor
11
+ async :name, :count, :slow_query, "slowly_increment", "raises_error"
12
+ attr_reader :name, :count
13
+
14
+ def initialize name, initial_value: 0
15
+ @name = name
16
+ @count = initial_value
17
+ end
18
+
19
+ protected
20
+
21
+ def slowly_increment
22
+ sleep 0.2
23
+ @count += 1
24
+ end
25
+
26
+ def slow_query
27
+ sleep 0.2
28
+ @count
29
+ end
30
+
31
+ def raises_error = raise "I'm an error"
32
+ end
33
+
34
+ class StepCounter < Counter
35
+ async :step_value
36
+ attr_reader :step_value
37
+
38
+ def initialize name, initial_value: 0, step_value: 5
39
+ super(name, initial_value: initial_value)
40
+ @step_value = step_value
41
+ end
42
+
43
+ protected
44
+
45
+ def slowly_increment
46
+ sleep 0.2
47
+ @count += @step_value
48
+ end
49
+
50
+ def failing_query
51
+ raise "I'm a failure"
52
+ end
53
+ end
54
+ # standard:enable Lint/ConstantDefinitionInBlock
55
+
56
+ it "knows which async messages are understood" do
57
+ expect(Counter.async_messages).to eq [:name, :count, :slow_query, :slowly_increment, :raises_error]
58
+ end
59
+
60
+ it "reuses existing proxy classes" do
61
+ @counter = Counter.start "inline counter", initial_value: 100
62
+ @proxy_class = @counter.class
63
+
64
+ @counter = Counter.start "another inline counter", initial_value: 200
65
+ expect(@counter.class).to eq @proxy_class
66
+ end
67
+
68
+ it "includes commands and queries from the superclass" do
69
+ expect(StepCounter.async_messages).to eq [:name, :count, :slow_query, :slowly_increment, :raises_error, :step_value]
70
+
71
+ @step_counter = StepCounter.start "step counter", initial_value: 100, step_value: 10
72
+
73
+ expect(@step_counter.count.await).to eq 100
74
+ expect(@step_counter.step_value.await).to eq 10
75
+ @step_counter.slowly_increment
76
+ expect(@step_counter.count.await).to eq 110
77
+ end
78
+
79
+ context "inline" do
80
+ around :example do |example|
81
+ Plumbing.configure mode: :inline, &example
82
+ end
83
+
84
+ it "returns the result from a message immediately" do
85
+ @counter = Counter.start "inline counter", initial_value: 100
86
+ @time = Time.now
87
+
88
+ expect(@counter.name.await).to eq "inline counter"
89
+ expect(@counter.count.await).to eq 100
90
+ expect(Time.now - @time).to be < 0.1
91
+
92
+ expect(@counter.slow_query.await).to eq 100
93
+ expect(Time.now - @time).to be > 0.1
94
+ end
95
+
96
+ it "sends all commands immediately" do
97
+ @counter = Counter.start "inline counter", initial_value: 100
98
+ @time = Time.now
99
+
100
+ @counter.slowly_increment
101
+
102
+ expect(@counter.count.await).to eq 101
103
+ expect(Time.now - @time).to be > 0.1
104
+ end
105
+ end
106
+
107
+ [:threaded, :async].each do |mode|
108
+ context mode.to_s do
109
+ around :example do |example|
110
+ Sync do
111
+ Plumbing.configure mode: mode, &example
112
+ end
113
+ end
114
+
115
+ it "performs queries in the background and waits for the response" do
116
+ @counter = Counter.start "async counter", initial_value: 100
117
+ @time = Time.now
118
+
119
+ expect(@counter.name.await).to eq "async counter"
120
+ expect(@counter.count.await).to eq 100
121
+ expect(Time.now - @time).to be < 0.1
122
+
123
+ expect(@counter.slow_query.await).to eq 100
124
+ expect(Time.now - @time).to be > 0.1
125
+ end
126
+
127
+ it "performs queries ignoring the response and returning immediately" do
128
+ @counter = Counter.start "threaded counter", initial_value: 100
129
+ @time = Time.now
130
+
131
+ @counter.slow_query
132
+
133
+ expect(Time.now - @time).to be < 0.1
134
+ end
135
+
136
+ it "performs commands in the background and returning immediately" do
137
+ @counter = Counter.start "threaded counter", initial_value: 100
138
+ @time = Time.now
139
+
140
+ @counter.slowly_increment
141
+ expect(Time.now - @time).to be < 0.1
142
+
143
+ # wait for the background task to complete
144
+ expect(101).to become_equal_to { @counter.count.await }
145
+ expect(Time.now - @time).to be > 0.1
146
+ end
147
+
148
+ it "re-raises exceptions when checking the result" do
149
+ @counter = Counter.start "failure"
150
+
151
+ expect { @counter.raises_error.await }.to raise_error "I'm an error"
152
+ end
153
+
154
+ it "does not raise exceptions if ignoring the result" do
155
+ @counter = Counter.start "failure"
156
+
157
+ expect { @counter.raises_error }.not_to raise_error
158
+ end
159
+ end
160
+ end
161
+
162
+ context "threaded" do
163
+ around :example do |example|
164
+ Plumbing.configure mode: :threaded, &example
165
+ end
166
+
167
+ # standard:disable Lint/ConstantDefinitionInBlock
168
+ class Record
169
+ include GlobalID::Identification
170
+ attr_reader :id
171
+ def initialize id
172
+ @id = id
173
+ end
174
+
175
+ def == other
176
+ other.id == @id
177
+ end
178
+ end
179
+
180
+ class Actor
181
+ include Plumbing::Actor
182
+ async :get_object_id, :get_object
183
+
184
+ private def get_object_id(record) = record.object_id
185
+ private def get_object(record) = record
186
+ end
187
+ # standard:enable Lint/ConstantDefinitionInBlock
188
+
189
+ it "packs and unpacks arguments when sending them across threads" do
190
+ @actor = Actor.start
191
+ @record = Record.new "999"
192
+
193
+ @object_id = @actor.get_object_id(@record).await
194
+
195
+ expect(@object_id).to_not eq @record.object_id
196
+ end
197
+
198
+ it "packs and unpacks results when sending them across threads" do
199
+ @actor = Actor.start
200
+ @record = Record.new "999"
201
+
202
+ @object = @actor.get_object(@record).await
203
+
204
+ expect(@object.id).to eq @record.id
205
+ expect(@object.object_id).to_not eq @record.object_id
206
+ end
207
+ end
208
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard-procedure-plumbing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rahoul Baruah
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-14 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-09-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: globalid
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: A composable event pipeline and sequential pipelines of operations
14
28
  email:
15
29
  - rahoulb@echodek.co
@@ -20,6 +34,13 @@ files:
20
34
  - README.md
21
35
  - Rakefile
22
36
  - lib/plumbing.rb
37
+ - lib/plumbing/actor.rb
38
+ - lib/plumbing/actor/async.rb
39
+ - lib/plumbing/actor/inline.rb
40
+ - lib/plumbing/actor/kernel.rb
41
+ - lib/plumbing/actor/rails.rb
42
+ - lib/plumbing/actor/threaded.rb
43
+ - lib/plumbing/actor/transporter.rb
23
44
  - lib/plumbing/config.rb
24
45
  - lib/plumbing/custom_filter.rb
25
46
  - lib/plumbing/error.rb
@@ -35,26 +56,21 @@ files:
35
56
  - lib/plumbing/rubber_duck/object.rb
36
57
  - lib/plumbing/rubber_duck/proxy.rb
37
58
  - lib/plumbing/types.rb
38
- - lib/plumbing/valve.rb
39
- - lib/plumbing/valve/async.rb
40
- - lib/plumbing/valve/inline.rb
41
- - lib/plumbing/valve/message.rb
42
- - lib/plumbing/valve/rails.rb
43
- - lib/plumbing/valve/threaded.rb
44
59
  - lib/plumbing/version.rb
45
60
  - spec/become_equal_to_matcher.rb
61
+ - spec/examples/actor_spec.rb
46
62
  - spec/examples/pipe_spec.rb
47
63
  - spec/examples/pipeline_spec.rb
48
64
  - spec/examples/rubber_duck_spec.rb
49
- - spec/examples/valve_spec.rb
50
65
  - spec/plumbing/a_pipe.rb
66
+ - spec/plumbing/actor/transporter_spec.rb
67
+ - spec/plumbing/actor_spec.rb
51
68
  - spec/plumbing/custom_filter_spec.rb
52
69
  - spec/plumbing/filter_spec.rb
53
70
  - spec/plumbing/junction_spec.rb
54
71
  - spec/plumbing/pipe_spec.rb
55
72
  - spec/plumbing/pipeline_spec.rb
56
73
  - spec/plumbing/rubber_duck_spec.rb
57
- - spec/plumbing/valve_spec.rb
58
74
  - spec/plumbing_spec.rb
59
75
  - spec/spec_helper.rb
60
76
  homepage: https://github.com/standard-procedure/plumbing
@@ -1,43 +0,0 @@
1
- require "async"
2
- require "async/semaphore"
3
- require "timeout"
4
-
5
- module Plumbing
6
- module Valve
7
- class Async
8
- attr_reader :target
9
-
10
- def initialize target
11
- @target = target
12
- @queue = []
13
- @semaphore = ::Async::Semaphore.new(1)
14
- end
15
-
16
- # Ask the target to answer the given message
17
- def ask(message, *args, **params, &block)
18
- task = @semaphore.async do
19
- @target.send message, *args, **params, &block
20
- end
21
- Timeout.timeout(timeout) do
22
- task.wait
23
- end
24
- end
25
-
26
- # Tell the target to execute the given message
27
- def tell(message, *args, **params, &block)
28
- @semaphore.async do |task|
29
- @target.send message, *args, **params, &block
30
- rescue
31
- nil
32
- end
33
- nil
34
- end
35
-
36
- private
37
-
38
- def timeout
39
- Plumbing.config.timeout
40
- end
41
- end
42
- end
43
- end
@@ -1,20 +0,0 @@
1
- module Plumbing
2
- module Valve
3
- class Inline
4
- def initialize target
5
- @target = target
6
- end
7
-
8
- # Ask the target to answer the given message
9
- def ask(message, ...)
10
- @target.send(message, ...)
11
- end
12
-
13
- # Tell the target to execute the given message
14
- def tell(message, ...)
15
- @target.send(message, ...)
16
- nil
17
- end
18
- end
19
- end
20
- end
@@ -1,5 +0,0 @@
1
- module Plumbing
2
- module Valve
3
- Message = Struct.new :message, :args, :params, :block, :result, :status
4
- end
5
- end