[DRE-commits] [ruby-aruba] 78/98: Re-integrate code of event-bus library

Hideki Yamane henrich at moszumanska.debian.org
Tue Mar 22 12:20:42 UTC 2016


This is an automated email from the git hooks/post-receive script.

henrich pushed a commit to branch debian/sid
in repository ruby-aruba.

commit 7f26e7323ec5c2738f12fbbe2a214bac9cc48db0
Author: Dennis Günnewig <dg1 at ratiodata.de>
Date:   Mon Jan 25 11:40:44 2016 +0100

    Re-integrate code of event-bus library
---
 History.md                           |   2 +-
 aruba.gemspec                        |   1 -
 lib/aruba/errors.rb                  |   6 ++
 lib/aruba/event_bus.rb               |  59 +++++++++++++
 lib/aruba/event_bus/name_resolver.rb | 162 +++++++++++++++++++++++++++++++++++
 lib/aruba/runtime.rb                 |   4 +-
 spec/event_bus/name_resolver_spec.rb |  68 +++++++++++++++
 spec/event_bus_spec.rb               | 160 ++++++++++++++++++++++++++++++++++
 8 files changed, 458 insertions(+), 4 deletions(-)

diff --git a/History.md b/History.md
index df84fc2..4d201dc 100644
--- a/History.md
+++ b/History.md
@@ -514,7 +514,7 @@
   default value for io_wait_timeout can be set correctly.
 * Make it possible to announce information on command error, using a new option 
   called `activate_announcer_on_command_failure` (PR #335, @njam)
-
+* Re-integrate `event-bus`-library into `aruba`-core (PR #342)
 
 ## [v1.0.0](https://github.com/cucumber/aruba/compare/v0.11.0...v1.0.0)
 
diff --git a/aruba.gemspec b/aruba.gemspec
index d5da5e6..e6af3a1 100644
--- a/aruba.gemspec
+++ b/aruba.gemspec
@@ -19,7 +19,6 @@ Gem::Specification.new do |s|
   s.add_runtime_dependency 'rspec-expectations', '>= 2.99'
   s.add_runtime_dependency 'contracts', '~> 0.9'
   s.add_runtime_dependency 'thor', '~> 0.19'
-  s.add_runtime_dependency 'event-bus', '~> 0.2'
 
   s.add_development_dependency 'bundler', '~> 1.11'
 
diff --git a/lib/aruba/errors.rb b/lib/aruba/errors.rb
index 10d9cde..c0043e9 100644
--- a/lib/aruba/errors.rb
+++ b/lib/aruba/errors.rb
@@ -28,4 +28,10 @@ module Aruba
   # Raised if command was already started, otherwise aruba forgets about the
   # previous pid and you've got hidden commands run
   class CommandAlreadyStartedError < Error; end
+
+  # Raised if an event name cannot be resolved
+  class EventNameResolveError < StandardError; end
+
+  # Raised if given object is not an event
+  class NoEventError < StandardError; end
 end
diff --git a/lib/aruba/event_bus.rb b/lib/aruba/event_bus.rb
new file mode 100644
index 0000000..ef18fac
--- /dev/null
+++ b/lib/aruba/event_bus.rb
@@ -0,0 +1,59 @@
+require 'aruba/event_bus/name_resolver'
+require 'aruba/errors'
+
+module Aruba
+  # Event bus
+  #
+  # Implements and in-process pub-sub events broadcaster allowing multiple observers
+  # to subscribe to different events that fire as your tests are executed.
+  #
+  class EventBus
+    # Create EventBus
+    #
+    # @param [#transform] resolver
+    #   A resolver which transforms Symbol, String, Class into an event Class.
+    def initialize(resolver)
+      @resolver = resolver
+      @handlers = Hash.new { |h, k| h[k] = [] }
+    end
+
+    # Register for an event
+    #
+    # @param [String, Symbol, Class, Array] event_ids
+    #   If Array, register multiple events witht the same handler. If String,
+    #   Symbol, Class register handler for given event.
+    #
+    # @param [#call] handler_object
+    #   The handler object, needs to have method `#call`. Either
+    #   `handler_object` or `block` can be defined. The handler object gets the
+    #   event passed to `#call`.
+    #
+    # @yield
+    #   Handler block which gets the event passed as parameter.
+    def register(event_ids, handler_object = nil, &handler_proc)
+      handler = handler_proc || handler_object
+
+      fail ArgumentError, 'Please pass either an object#call or a handler block' if handler.nil? || !handler.respond_to?(:call)
+
+      Array(event_ids).flatten.each do |id|
+        @handlers[
+          @resolver.transform(id).to_s
+        ] << handler
+      end
+
+      nil
+    end
+
+    # Broadcast an event
+    #
+    # @param [Object] event
+    #   An object of registered event class. This object is passed to the event
+    #   handler.
+    #
+    def notify(event)
+      fail NoEventError, 'Please pass an event object, not a class' if event.is_a?(Class)
+
+      @handlers[event.class.to_s].each { |handler| handler.call(event) }
+    end
+  end
+end
diff --git a/lib/aruba/event_bus/name_resolver.rb b/lib/aruba/event_bus/name_resolver.rb
new file mode 100644
index 0000000..4a035dd
--- /dev/null
+++ b/lib/aruba/event_bus/name_resolver.rb
@@ -0,0 +1,162 @@
+require 'aruba/errors'
+
+# Event notification library
+module Aruba
+  # EventBus
+  class EventBus
+    # Resolve name to Event name
+    class NameResolver
+      # @private
+      # Helpers for Resolvers
+      module ResolveHelpers
+        def camel_case(underscored_name)
+          if RUBY_VERSION < '1.9.3'
+            underscored_name.to_s.split('_').map { |word| word.upcase.chars.to_a[0] + word.chars.to_a[1..-1].join }.join
+          else
+            underscored_name.to_s.split('_').map { |word| word.upcase[0] + word[1..-1] }.join
+          end
+        end
+
+        # Thanks ActiveSupport
+        # (Only needed to support Ruby 1.9.3 and JRuby)
+        def constantize(camel_cased_word)
+          names = camel_cased_word.split('::')
+
+          # Trigger a built-in NameError exception including the ill-formed constant in the message.
+          Object.const_get(camel_cased_word) if names.empty?
+
+          # Remove the first blank element in case of '::ClassName' notation.
+          names.shift if names.size > 1 && names.first.empty?
+
+          names.inject(Object) do |constant, name|
+            if constant == Object
+              constant.const_get(name)
+            else
+              candidate = constant.const_get(name)
+
+              if RUBY_VERSION < '1.9.3'
+                next candidate if constant.const_defined?(name)
+              else
+                next candidate if constant.const_defined?(name, false)
+              end
+
+              next candidate unless Object.const_defined?(name)
+
+              # Go down the ancestors to check if it is owned directly. The check
+              # stops when we reach Object or the end of ancestors tree.
+              # rubocop:disable Style/EachWithObject
+              constant = constant.ancestors.inject do |const, ancestor|
+                break const    if ancestor == Object
+                break ancestor if ancestor.const_defined?(name, false)
+                const
+              end
+              # rubocop:enable Style/EachWithObject
+
+              # owner is in Object, so raise
+              constant.const_get(name, false)
+            end
+          end
+        end
+      end
+
+      # @private
+      # Convert a class in to an event class
+      class ClassResolver
+        class << self
+          def match?(event_id)
+            event_id.is_a? Class
+          end
+
+          # Which types are supported
+          def supports
+            [Class]
+          end
+        end
+
+        def transform(_, event_id)
+          event_id
+        end
+      end
+
+      # @private
+      # Convert a string in to an event class
+      class StringResolver
+        include ResolveHelpers
+
+        class << self
+          def match?(event_id)
+            event_id.is_a? String
+          end
+
+          # Which types are supported
+          def supports
+            [String]
+          end
+        end
+
+        def transform(_, event_id)
+          constantize(event_id)
+        end
+      end
+
+      # @private
+      # Convert a symbol in to an event class
+      class SymbolResolver
+        include ResolveHelpers
+
+        class << self
+          def match?(event_id)
+            event_id.is_a? Symbol
+          end
+
+          # Which types are supported
+          def supports
+            [Symbol]
+          end
+        end
+
+        def transform(default_namespace, event_id)
+          constantize("#{default_namespace}::#{camel_case(event_id)}")
+        end
+      end
+
+      # @private
+      # Default failing resolver
+      #
+      # This comes into play if the user passes an invalid event type
+      class FailingResolver
+        class << self
+          def match?(event_id)
+            fail ArgumentError, %(Input type "#{event_id.class}" of event_id "#{event_id}" is invalid)
+          end
+
+          def supports
+            []
+          end
+        end
+      end
+
+      protected
+
+      attr_reader :resolvers, :default_namespace
+
+      public
+
+      def initialize(default_namespace)
+        @default_namespace = default_namespace
+
+        @resolvers = []
+        @resolvers << ClassResolver
+        @resolvers << StringResolver
+        @resolvers << SymbolResolver
+        @resolvers << FailingResolver
+      end
+
+      def transform(event_id)
+        resolvers.find { |r| r.match? event_id }.new.transform(default_namespace, event_id)
+      rescue => e
+        raise EventNameResolveError, %(Transforming "#{event_id}" into an event class failed. Supported types are: #{@resolvers.map(&:supports).flatten.join(', ')}. #{e.message}.\n\n#{e.backtrace.join("\n")})
+      end
+    end
+  end
+end
diff --git a/lib/aruba/runtime.rb b/lib/aruba/runtime.rb
index 0b0294f..0d6491d 100644
--- a/lib/aruba/runtime.rb
+++ b/lib/aruba/runtime.rb
@@ -2,7 +2,7 @@ require 'aruba/config'
 require 'aruba/aruba_path'
 require 'aruba/config_wrapper'
 require 'aruba/events'
-require 'event/bus'
+require 'aruba/event_bus'
 
 module Aruba
   # Runtime of aruba
@@ -40,7 +40,7 @@ module Aruba
     attr_accessor :config, :environment, :logger, :command_monitor, :announcer, :event_bus
 
     def initialize(opts = {})
-      @event_bus       = ::Event::Bus.new(::Event::NameResolver.new(Aruba::Events))
+      @event_bus       = EventBus.new(EventBus::NameResolver.new(Aruba::Events))
       @announcer       = opts.fetch(:announcer, Aruba.platform.announcer.new)
       @config          = opts.fetch(:config, ConfigWrapper.new(Aruba.config.make_copy, @event_bus))
       @environment     = opts.fetch(:environment, Aruba.platform.environment_variables.new)
diff --git a/spec/event_bus/name_resolver_spec.rb b/spec/event_bus/name_resolver_spec.rb
new file mode 100644
index 0000000..ca971cf
--- /dev/null
+++ b/spec/event_bus/name_resolver_spec.rb
@@ -0,0 +1,68 @@
+require 'aruba/event_bus/name_resolver'
+
+describe Aruba::EventBus::NameResolver do
+  subject(:resolver) { described_class.new(default_name_space) }
+  let(:default_name_space) { 'Events' }
+  let(:resolved_name) { resolver.transform(original_name) }
+
+  before :each do
+    stub_const('Events::MyEvent', Class.new)
+    stub_const('Events::MyEvent', Class.new)
+  end
+
+  describe '#transform' do
+    context 'when name is string' do
+      context 'when simple' do
+        let(:original_name) { 'Events::MyEvent' }
+        it { expect(resolved_name).to eq Events::MyEvent }
+      end
+
+      context 'when prefixed' do
+        let(:original_name) { '::Events::MyEvent' }
+        it { expect(resolved_name).to eq Events::MyEvent }
+      end
+    end
+
+    context 'when name is class' do
+      context 'when simple' do
+        let(:original_name) { Events::MyEvent }
+        it { expect(resolved_name).to eq Events::MyEvent }
+      end
+
+      context 'when prefixed' do
+        let(:original_name) { ::Events::MyEvent }
+        it { expect(resolved_name).to eq Events::MyEvent }
+      end
+    end
+
+    context 'when name is symbol' do
+      let(:original_name) { :my_event }
+      it { expect(resolved_name).to eq Events::MyEvent }
+    end
+
+    context 'when namespace ...' do
+      before :each do
+        stub_const('MyLib::Events::MyEvent', Class.new)
+      end
+
+      context 'when is string' do
+        let!(:default_name_space) { 'MyLib::Events' }
+        let!(:original_name) { :my_event }
+
+        it { expect(resolved_name).to eq MyLib::Events::MyEvent }
+      end
+
+      context 'when is module' do
+        let!(:default_name_space) { MyLib::Events }
+        let!(:original_name) { :my_event }
+
+        it { expect(resolved_name).to eq MyLib::Events::MyEvent }
+      end
+    end
+
+    context 'when invalid' do
+      let(:original_name) { 1 }
+      it { expect { resolved_name }.to raise_error Aruba::EventNameResolveError, /Transforming "1" into an event class failed. Supported types are: Class, String, Symbol. Input type "Fixnum" of event_id "1" is invalid./ }
+    end
+  end
+end
diff --git a/spec/event_bus_spec.rb b/spec/event_bus_spec.rb
new file mode 100644
index 0000000..64e174e
--- /dev/null
+++ b/spec/event_bus_spec.rb
@@ -0,0 +1,160 @@
+require 'aruba/event_bus'
+
+# rubocop:disable Style/Documentation
+module Events
+  class TestEvent; end
+  class AnotherTestEvent; end
+  module MalformedTestEvent; end
+end
+
+class MyHandler
+  def call(*); end
+end
+
+class MyMalformedHandler; end
+# rubocop:enable Style/Documentation
+
+describe Aruba::EventBus do
+  subject(:bus) { described_class.new(name_resolver) }
+
+  let(:name_resolver) { instance_double('Events::NameResolver') }
+
+  let!(:event_klass) { Events::TestEvent }
+  let!(:event_name) { event_klass }
+  let!(:event_instance) { Events::TestEvent.new }
+
+  let!(:another_event_klass) { Events::AnotherTestEvent }
+  let!(:another_event_name) { another_event_klass }
+  let!(:another_event_instance) { Events::AnotherTestEvent.new }
+
+  describe '#notify' do
+    before(:each) do
+      allow(name_resolver).to receive(:transform).with(event_name).and_return(event_klass)
+    end
+
+    context 'when subscriber to event, the block is called and get\'s an instance of the event passed as payload' do
+      before :each do
+        bus.register(event_klass) do |event|
+          @received_payload = event
+        end
+
+        bus.notify event_instance
+      end
+
+      it { expect(@received_payload).to eq(event_instance) }
+    end
+
+    context 'when not subscriber to event' do
+      before :each do
+        @received_payload = false
+        bus.register(event_klass) { @received_payload = true }
+        bus.notify another_event_instance
+      end
+
+      it { expect(@received_payload).to eq(false) }
+    end
+
+    context 'when event is not an instance of event class' do
+      let!(:event_name) { :test_event }
+      let(:received_payload) { [] }
+
+      before :each do
+        bus.register(event_name, proc {})
+      end
+
+      it { expect { bus.notify event_klass }.to raise_error Aruba::NoEventError }
+    end
+  end
+
+  describe '#register' do
+    before(:each) do
+      allow(name_resolver).to receive(:transform).with(event_name).and_return(event_klass)
+    end
+
+    context 'when valid subscriber' do
+      context 'when multiple instances are given' do
+        let(:received_events) { [] }
+
+        before :each do
+          bus.register(Events::TestEvent) do |event|
+            received_events << event
+          end
+          bus.register(Events::TestEvent) do |event|
+            received_events << event
+          end
+
+          bus.notify event_instance
+        end
+
+        it { expect(received_events.length).to eq 2 }
+        it { expect(received_events).to all eq event_instance }
+      end
+
+      context 'when is string' do
+        let!(:event_name) { event_klass.to_s }
+        let(:received_payload) { [] }
+
+        before :each do
+          bus.register(event_klass.to_s) do |event|
+            received_payload << event
+          end
+
+          bus.notify event_instance
+        end
+
+        it { expect(received_payload).to include event_instance }
+      end
+
+      context 'when is symbol and event is defined in the default namespace given to NameResolver.new' do
+        let!(:event_name) { :test_event }
+        let(:received_payload) { [] }
+
+        before :each do
+          bus.register(event_name) do |event|
+            received_payload << event
+          end
+
+          bus.notify event_instance
+        end
+
+        it { expect(received_payload).to include event_instance }
+      end
+    end
+
+    context 'when valid custom handler' do
+      context 'when single event class' do
+        before(:each) do
+          allow(name_resolver).to receive(:transform).with(event_name).and_return(event_klass)
+        end
+
+        before :each do
+          bus.register(event_klass, MyHandler.new)
+        end
+
+        it { expect { bus.notify event_instance }.not_to raise_error }
+      end
+
+      context 'when list of event classes' do
+        before(:each) do
+          allow(name_resolver).to receive(:transform).with(event_name).and_return(event_klass)
+          allow(name_resolver).to receive(:transform).with(another_event_name).and_return(another_event_klass)
+        end
+
+        before :each do
+          bus.register([event_klass, another_event_klass], MyHandler.new)
+        end
+
+        it { expect { bus.notify event_instance }.not_to raise_error }
+        it { expect { bus.notify another_event_instance }.not_to raise_error }
+      end
+    end
+
+    context 'when malformed custom handler' do
+      it { expect { bus.register(event_klass, MyMalformedHandler.new) }.to raise_error ArgumentError }
+    end
+
+    context 'when no handler is given' do
+      it { expect { bus.register(event_klass) }.to raise_error ArgumentError }
+    end
+  end
+end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-aruba.git



More information about the Pkg-ruby-extras-commits mailing list