standard-procedure-plumbing 0.4.3 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,7 @@ module Plumbing
8
8
  nil
9
9
  end
10
10
 
11
- def within_actor? = proxy.within_actor?
11
+ def in_context? = proxy.in_context?
12
12
 
13
13
  def stop = proxy.stop
14
14
 
@@ -1,6 +1,6 @@
1
1
  # Pipes, pipelines, actors and rubber ducks
2
2
  module Plumbing
3
- Config = Data.define :mode, :actor_proxy_classes, :timeout do
3
+ Config = Data.define :mode, :actor_proxy_classes, :timeout, :debug do
4
4
  def actor_proxy_class_for target_class
5
5
  actor_proxy_classes[target_class]
6
6
  end
@@ -45,7 +45,7 @@ module Plumbing
45
45
  private_class_method :set_configuration_and_yield
46
46
 
47
47
  def self.configs
48
- @configs ||= [Config.new(mode: :inline, timeout: 30, actor_proxy_classes: {})]
48
+ @configs ||= [Config.new(mode: :inline, timeout: 30, actor_proxy_classes: {}, debug: false)]
49
49
  end
50
50
  private_class_method :configs
51
51
  end
@@ -0,0 +1,121 @@
1
+ require "rspec/expectations"
2
+
3
+ # Custom matcher that repeatedly evaluates the block until it matches the expected value or the timeout has elapsed
4
+ #
5
+ # This allows asynchronous operations to be tested in a synchronous manner with a timeout
6
+ #
7
+ # Example:
8
+ # expect{ subject.greeting }.to become "Hello Alice"
9
+ #
10
+ RSpec::Matchers.define :become do |expected|
11
+ match do |block|
12
+ wait_for do
13
+ (@result = block.call) == expected
14
+ end
15
+ true
16
+ rescue Timeout::Error
17
+ false
18
+ end
19
+
20
+ def supports_block_expectations? = true
21
+
22
+ failure_message do |expected|
23
+ "expected #{expected} but, after timeout, the result was #{@result}"
24
+ end
25
+ end
26
+
27
+ # Custom matcher that repeatedly evaluates the block until it becomes true or the timeout has elapsed
28
+ #
29
+ # This allows asynchronous operations to be tested in a synchronous manner with a timeout
30
+ #
31
+ # Example:
32
+ # expect { @object.valid? }.to become_true
33
+ #
34
+ RSpec::Matchers.define :become_true do
35
+ match do |block|
36
+ wait_for do
37
+ block.call == true
38
+ end
39
+ true
40
+ rescue Timeout::Error
41
+ false
42
+ end
43
+
44
+ def supports_block_expectations? = true
45
+
46
+ failure_message do |expected|
47
+ "expected true but timeout expired"
48
+ end
49
+ end
50
+
51
+ # Custom matcher that repeatedly evaluates the block until it becomes truthy or the timeout has elapsed
52
+ #
53
+ # This allows asynchronous operations to be tested in a synchronous manner with a timeout
54
+ #
55
+ # Example:
56
+ # expect { @object.message }.to become_truthy
57
+ #
58
+ RSpec::Matchers.define :become_truthy do
59
+ match do |block|
60
+ wait_for do
61
+ block.call
62
+ end
63
+ true
64
+ rescue Timeout::Error
65
+ false
66
+ end
67
+
68
+ def supports_block_expectations? = true
69
+
70
+ failure_message do |expected|
71
+ "expected truthy value but timeout expired"
72
+ end
73
+ end
74
+
75
+ # Custom matcher that repeatedly evaluates the block until it becomes false or the timeout has elapsed
76
+ #
77
+ # This allows asynchronous operations to be tested in a synchronous manner with a timeout
78
+ #
79
+ # Example:
80
+ # expect { @object.in_progress? }.to become_false
81
+ #
82
+ RSpec::Matchers.define :become_false do
83
+ match do |block|
84
+ wait_for do
85
+ block.call == false
86
+ end
87
+ true
88
+ rescue Timeout::Error
89
+ false
90
+ end
91
+
92
+ def supports_block_expectations? = true
93
+
94
+ failure_message do |expected|
95
+ "expected false but timeout expired"
96
+ end
97
+ end
98
+
99
+ # Custom matcher that repeatedly evaluates the block until it becomes falsey or the timeout has elapsed
100
+ #
101
+ # This allows asynchronous operations to be tested in a synchronous manner with a timeout
102
+ #
103
+ # Example:
104
+ # expect { @object.background_task }.to become_falsey
105
+ #
106
+ RSpec::Matchers.define :become_falsey do
107
+ match do |block|
108
+ wait_for do
109
+ !block.call
110
+ end
111
+ true
112
+ rescue Timeout::Error
113
+ false
114
+ end
115
+
116
+ def supports_block_expectations? = true
117
+
118
+ failure_message do |expected|
119
+ "expected falsey but timeout expired"
120
+ end
121
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plumbing
4
- VERSION = "0.4.3"
4
+ VERSION = "0.4.5"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard-procedure-plumbing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.5
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-17 00:00:00.000000000 Z
11
+ date: 2024-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: globalid
@@ -31,6 +31,7 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - LICENSE
34
35
  - README.md
35
36
  - Rakefile
36
37
  - lib/plumbing.rb
@@ -55,25 +56,9 @@ files:
55
56
  - lib/plumbing/rubber_duck/module.rb
56
57
  - lib/plumbing/rubber_duck/object.rb
57
58
  - lib/plumbing/rubber_duck/proxy.rb
59
+ - lib/plumbing/spec/become_matchers.rb
58
60
  - lib/plumbing/types.rb
59
61
  - lib/plumbing/version.rb
60
- - spec/become_equal_to_matcher.rb
61
- - spec/examples/actor_spec.rb
62
- - spec/examples/await_spec.rb
63
- - spec/examples/pipe_spec.rb
64
- - spec/examples/pipeline_spec.rb
65
- - spec/examples/rubber_duck_spec.rb
66
- - spec/plumbing/a_pipe.rb
67
- - spec/plumbing/actor/transporter_spec.rb
68
- - spec/plumbing/actor_spec.rb
69
- - spec/plumbing/custom_filter_spec.rb
70
- - spec/plumbing/filter_spec.rb
71
- - spec/plumbing/junction_spec.rb
72
- - spec/plumbing/pipe_spec.rb
73
- - spec/plumbing/pipeline_spec.rb
74
- - spec/plumbing/rubber_duck_spec.rb
75
- - spec/plumbing_spec.rb
76
- - spec/spec_helper.rb
77
62
  homepage: https://github.com/standard-procedure/plumbing
78
63
  licenses: []
79
64
  metadata:
@@ -96,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
81
  - !ruby/object:Gem::Version
97
82
  version: '0'
98
83
  requirements: []
99
- rubygems_version: 3.5.12
84
+ rubygems_version: 3.5.17
100
85
  signing_key:
101
86
  specification_version: 4
102
87
  summary: Plumbing - various pipelines for your ruby application
@@ -1,26 +0,0 @@
1
- require "rspec/expectations"
2
-
3
- # Custom matcher that repeatedly evaluates the block until it matches the expected value or 5 seconds have elapsed
4
- #
5
- # This allows asynchronous operations to be tested in a synchronous manner with a timeout
6
- #
7
- # Example:
8
- # expect("Hello").to become_equal_to { subject.greeting }
9
- #
10
- RSpec::Matchers.define :become_equal_to do
11
- match do |expected|
12
- max = Plumbing.config.timeout * 10
13
- counter = 0
14
- matched = false
15
- while (counter < max) && (matched == false)
16
- sleep 0.1
17
- counter += 1
18
- matched = true if (@result = block_arg.call) == expected
19
- end
20
- matched
21
- end
22
-
23
- failure_message do |expected|
24
- "expected block to return #{expected} but was #{@result} after timeout expired"
25
- end
26
- end
@@ -1,86 +0,0 @@
1
- require "spec_helper"
2
- require "plumbing/actor/async"
3
- require "plumbing/actor/threaded"
4
-
5
- RSpec.shared_examples "an example actor" do |runs_in_background|
6
- # standard:disable Lint/ConstantDefinitionInBlock
7
- class Employee
8
- include Plumbing::Actor
9
- async :name, :job_title, :greet_slowly, :promote
10
-
11
- def initialize(name)
12
- @name = name
13
- @job_title = "Sales assistant"
14
- end
15
-
16
- attr_reader :name, :job_title
17
-
18
- def promote
19
- sleep 0.5
20
- @job_title = "Sales manager"
21
- end
22
-
23
- def greet_slowly
24
- sleep 0.2
25
- "H E L L O"
26
- end
27
- end
28
- # standard:enable Lint/ConstantDefinitionInBlock
29
-
30
- it "queries an object" do
31
- @person = Employee.start "Alice"
32
-
33
- expect(await { @person.name }).to eq "Alice"
34
- expect(await { @person.job_title }).to eq "Sales assistant"
35
-
36
- @time = Time.now
37
- # `greet_slowly` is a query so will block until a response is received
38
- expect(await { @person.greet_slowly }).to eq "H E L L O"
39
- expect(Time.now - @time).to be > 0.1
40
-
41
- @time = Time.now
42
- # we're not awaiting the result, so this should run in the background (unless we're using inline mode)
43
- @person.greet_slowly
44
-
45
- expect(Time.now - @time).to be < 0.1 if runs_in_background
46
- expect(Time.now - @time).to be > 0.1 if !runs_in_background
47
- ensure
48
- @person.stop
49
- end
50
-
51
- it "commands an object" do
52
- @person = Employee.start "Alice"
53
- @person.promote
54
- expect(@person.job_title.value).to eq "Sales manager"
55
- ensure
56
- @person.stop
57
- end
58
- end
59
-
60
- RSpec.describe "Actor example: " do
61
- context "inline mode" do
62
- around :example do |example|
63
- Plumbing.configure mode: :inline, &example
64
- end
65
-
66
- it_behaves_like "an example actor", false
67
- end
68
-
69
- context "async mode" do
70
- around :example do |example|
71
- Plumbing.configure mode: :async do
72
- Kernel::Async(&example)
73
- end
74
- end
75
-
76
- it_behaves_like "an example actor", true
77
- end
78
-
79
- context "threaded mode" do
80
- around :example do |example|
81
- Plumbing.configure mode: :threaded, &example
82
- end
83
-
84
- it_behaves_like "an example actor", true
85
- end
86
- end
@@ -1,43 +0,0 @@
1
- require "spec_helper"
2
- require "plumbing/actor/async"
3
- require "plumbing/actor/threaded"
4
-
5
- RSpec.describe "await" do
6
- # standard:disable Lint/ConstantDefinitionInBlock
7
- class Person
8
- include Plumbing::Actor
9
- async :name
10
- def initialize name
11
- @name = name
12
- end
13
- attr_reader :name
14
- end
15
- # standard:enable Lint/ConstantDefinitionInBlock
16
-
17
- [:inline, :async, :threaded].each do |mode|
18
- context "#{mode} mode" do
19
- around :example do |example|
20
- Sync do
21
- Plumbing.configure mode: mode, &example
22
- end
23
- end
24
-
25
- it "awaits a result from the actor directly" do
26
- @person = Person.start "Alice"
27
-
28
- expect(@person.name.value).to eq "Alice"
29
- end
30
-
31
- it "uses a block to await the result from the actor" do
32
- @person = Person.start "Alice"
33
-
34
- expect(await { @person.name }).to eq "Alice"
35
- end
36
-
37
- it "uses a block to immediately access non-actor objects" do
38
- @person = "Bob"
39
- expect(await { @person }).to eq "Bob"
40
- end
41
- end
42
- end
43
- end
@@ -1,145 +0,0 @@
1
- require "spec_helper"
2
- require "async"
3
- require "plumbing/actor/async"
4
- require "plumbing/actor/threaded"
5
-
6
- RSpec.describe "Pipe examples" do
7
- it "observes events" do
8
- @source = Plumbing::Pipe.start
9
-
10
- @result = []
11
- @source.add_observer do |event|
12
- @result << event.type
13
- end
14
-
15
- @source.notify "something_happened", message: "But what was it?"
16
- expect(@result).to eq ["something_happened"]
17
- end
18
-
19
- it "filters events" do
20
- @source = Plumbing::Pipe.start
21
-
22
- @filter = Plumbing::Filter.start source: @source do |event|
23
- %w[important urgent].include? event.type
24
- end
25
-
26
- @result = []
27
- @filter.add_observer do |event|
28
- @result << event.type
29
- end
30
-
31
- @source.notify "important", message: "ALERT! ALERT!"
32
- expect(@result).to eq ["important"]
33
-
34
- @source.notify "unimportant", message: "Nothing to see here"
35
- expect(@result).to eq ["important"]
36
- end
37
-
38
- it "allows for custom filters" do
39
- # standard:disable Lint/ConstantDefinitionInBlock
40
- class EveryThirdEvent < Plumbing::CustomFilter
41
- def initialize source:
42
- super
43
- @events = []
44
- end
45
-
46
- def received event
47
- safely do
48
- @events << event
49
- if @events.count >= 3
50
- @events.clear
51
- self << event
52
- end
53
- end
54
- end
55
- end
56
- # standard:enable Lint/ConstantDefinitionInBlock
57
-
58
- @source = Plumbing::Pipe.start
59
- @filter = EveryThirdEvent.start(source: @source)
60
-
61
- @result = []
62
- @filter.add_observer do |event|
63
- @result << event.type
64
- end
65
-
66
- 1.upto 10 do |i|
67
- @source.notify i.to_s
68
- end
69
-
70
- expect(@result).to eq ["3", "6", "9"]
71
- end
72
-
73
- it "joins multiple source pipes" do
74
- @first_source = Plumbing::Pipe.start
75
- @second_source = Plumbing::Pipe.start
76
-
77
- @junction = Plumbing::Junction.start @first_source, @second_source
78
-
79
- @result = []
80
- @junction.add_observer do |event|
81
- @result << event.type
82
- end
83
-
84
- @first_source.notify "one"
85
- expect(@result).to eq ["one"]
86
- @second_source.notify "two"
87
- expect(@result).to eq ["one", "two"]
88
- end
89
-
90
- it "dispatches events asynchronously using async" do
91
- Plumbing.configure mode: :async do
92
- Sync do
93
- @first_source = Plumbing::Pipe.start
94
- @second_source = Plumbing::Pipe.start
95
- @junction = Plumbing::Junction.start @first_source, @second_source
96
- @filter = Plumbing::Filter.start source: @junction do |event|
97
- %w[one-one two-two].include? event.type
98
- end
99
- @result = []
100
- @filter.add_observer do |event|
101
- @result << event.type
102
- end
103
-
104
- @first_source.notify "one-one"
105
- @first_source.notify "one-two"
106
- @second_source.notify "two-one"
107
- @second_source.notify "two-two"
108
-
109
- expect(["one-one", "two-two"]).to become_equal_to { @result }
110
- end
111
- end
112
- end
113
-
114
- it "dispatches events asynchronously using threads" do
115
- Plumbing.configure mode: :threaded do
116
- @result = []
117
-
118
- @first_source = Plumbing::Pipe.start
119
- @second_source = Plumbing::Pipe.start
120
- @junction = Plumbing::Junction.start @first_source, @second_source
121
-
122
- @filter = Plumbing::Filter.start source: @junction do |event|
123
- %w[one-one two-two].include? event.type
124
- end
125
- await do
126
- @filter.add_observer do |event|
127
- puts "observing #{event.type}"
128
- @result << event.type
129
- end
130
- end
131
-
132
- @first_source.notify "one-one"
133
- @first_source.notify "one-two"
134
- @second_source.notify "two-one"
135
- @second_source.notify "two-two"
136
-
137
- expect(["one-one", "two-two"]).to become_equal_to { @result.sort }
138
- ensure
139
- @first_source.shutdown
140
- @second_source.shutdown
141
- @junction.shutdown
142
- @filter.shutdown
143
- end
144
- end
145
- end
@@ -1,89 +0,0 @@
1
- require "spec_helper"
2
- require "dry/validation"
3
-
4
- RSpec.describe "Pipeline examples" do
5
- it "builds a simple pipeline of operations adding to an array with pre-conditions and post-conditions" do
6
- # standard:disable Lint/ConstantDefinitionInBlock
7
- class BuildArray < Plumbing::Pipeline
8
- perform :add_first
9
- perform :add_second
10
- perform :add_third
11
-
12
- pre_condition :must_be_an_array do |input|
13
- input.is_a? Array
14
- end
15
-
16
- post_condition :must_have_three_elements do |output|
17
- output.length == 3
18
- end
19
-
20
- private
21
-
22
- def add_first(input) = input << "first"
23
-
24
- def add_second(input) = input << "second"
25
-
26
- def add_third(input) = input << "third"
27
- end
28
- # standard:enable Lint/ConstantDefinitionInBlock
29
-
30
- expect(BuildArray.new.call([])).to eq ["first", "second", "third"]
31
- expect { BuildArray.new.call(1) }.to raise_error(Plumbing::PreConditionError)
32
- expect { BuildArray.new.call(["extra element"]) }.to raise_error(Plumbing::PostConditionError)
33
- end
34
-
35
- it "builds a simple pipeline of operations using an external class to implement one of the steps" do
36
- # standard:disable Lint/ConstantDefinitionInBlock
37
- class ExternalStep < Plumbing::Pipeline
38
- perform :add_item_to_array
39
-
40
- private
41
-
42
- def add_item_to_array(input) = input << "external"
43
- end
44
-
45
- class BuildSequenceWithExternalStep < Plumbing::Pipeline
46
- perform :add_first
47
- perform :add_second, using: "ExternalStep"
48
- perform :add_third
49
-
50
- private
51
-
52
- def add_first(input) = input << "first"
53
-
54
- def add_third(input) = input << "third"
55
- end
56
- # standard:enable Lint/ConstantDefinitionInBlock
57
-
58
- expect(BuildSequenceWithExternalStep.new.call([])).to eq ["first", "external", "third"]
59
- end
60
-
61
- it "uses a dry-validation contract to test the input parameters" do
62
- # standard:disable Lint/ConstantDefinitionInBlock
63
- class SayHello < Plumbing::Pipeline
64
- validate_with "SayHello::Input"
65
- perform :say_hello
66
-
67
- private
68
-
69
- def say_hello input
70
- "Hello #{input[:name]} - I will now send a load of annoying marketing messages to #{input[:email]}"
71
- end
72
-
73
- class Input < Dry::Validation::Contract
74
- params do
75
- required(:name).filled(:string)
76
- required(:email).filled(:string)
77
- end
78
- rule :email do
79
- key.failure("must be a valid email") unless /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match? value
80
- end
81
- end
82
- end
83
- # standard:enable Lint/ConstantDefinitionInBlock
84
-
85
- SayHello.new.call(name: "Alice", email: "alice@example.com")
86
-
87
- expect { SayHello.new.call(some: "other data") }.to raise_error(Plumbing::PreConditionError)
88
- end
89
- end