aboutsummaryrefslogblamecommitdiffstats
path: root/activesupport/test/notifications_test.rb
blob: 5cfbd60131be641895c35344db873d59c59ce606 (plain) (tree)
1
2
3
4
5
6
7
8
                             
 

                                                   
 


                                          


                                                           
                  
                        
                                                                             
                                                                                                               
       
 



                                                           
         
 


                                                    
     
 






                                 
                                                  









                                                                






                                                                                      
                                                                       
 

                                                        



                                                          

     
                                  
                                                 



                                          
                            


                                    
 



















                                                                                        



                    

     













                                               
                                 




                                               
 










                                                   
                                                    









                                                                 
                                       
                 
                                                         
 


                             
                    
 
                                  
       
 
                                        

                                                          
 


                             
                    
 
                                                    
       
 
                                     



                                                      
 

                                     

       







                                                 



                      

     
                                      
                                                          
 
                                            
                                                    

       
                                                                   
                                                                     

                                               
                                                         

       
                                        
                                                                        

       
                                              

                                                       

               
 

                                             
                                                                  

         

                                              
                                                                       

       
                                                          
           
                                                         
                      
           
                              
                                      
         
 
                                  

                                                                                       
       
 
                                               
                                                    

                                              
                                                                       
       

     

                                                
                     
                                                           
 

                                      
                                                   
       
 
                                                         

                                                                           

       
                                              
                                            
 


                                                                                  
 

                              




                                          
 
           
                   
                                           
         

     
# frozen_string_literal: true

require "abstract_unit"
require "active_support/core_ext/module/delegation"

module Notifications
  class TestCase < ActiveSupport::TestCase
    def setup
      @old_notifier = ActiveSupport::Notifications.notifier
      @notifier = ActiveSupport::Notifications::Fanout.new
      ActiveSupport::Notifications.notifier = @notifier
      @events = []
      @named_events = []
      @subscription = @notifier.subscribe { |*args| @events << event(*args) }
      @named_subscription = @notifier.subscribe("named.subscription") { |*args| @named_events << event(*args) }
    end

    def teardown
      ActiveSupport::Notifications.notifier = @old_notifier
    end

  private

    def event(*args)
      ActiveSupport::Notifications::Event.new(*args)
    end
  end

  class SubscribedTest < TestCase
    def test_subscribed
      name     = "foo"
      name2    = name * 2
      expected = [name, name]

      events   = []
      callback = lambda { |*_| events << _.first }
      ActiveSupport::Notifications.subscribed(callback, name) do
        ActiveSupport::Notifications.instrument(name)
        ActiveSupport::Notifications.instrument(name2)
        ActiveSupport::Notifications.instrument(name)
      end
      assert_equal expected, events

      ActiveSupport::Notifications.instrument(name)
      assert_equal expected, events
    end

    def test_subsribing_to_instrumentation_while_inside_it
      # the repro requires that there are no evented subscribers for the "foo" event,
      # so we have to duplicate some of the setup code
      old_notifier = ActiveSupport::Notifications.notifier
      ActiveSupport::Notifications.notifier = ActiveSupport::Notifications::Fanout.new

      ActiveSupport::Notifications.subscribe("foo", TestSubscriber.new)

      ActiveSupport::Notifications.instrument("foo") do
        ActiveSupport::Notifications.subscribe("foo") {}
      end
    ensure
      ActiveSupport::Notifications.notifier = old_notifier
    end
  end

  class UnsubscribeTest < TestCase
    def test_unsubscribing_removes_a_subscription
      @notifier.publish :foo
      @notifier.wait
      assert_equal [[:foo]], @events
      @notifier.unsubscribe(@subscription)
      @notifier.publish :foo
      @notifier.wait
      assert_equal [[:foo]], @events
    end

    def test_unsubscribing_by_name_removes_a_subscription
      @notifier.publish "named.subscription", :foo
      @notifier.wait
      assert_equal [["named.subscription", :foo]], @named_events
      @notifier.unsubscribe("named.subscription")
      @notifier.publish "named.subscription", :foo
      @notifier.wait
      assert_equal [["named.subscription", :foo]], @named_events
    end

    def test_unsubscribing_by_name_leaves_the_other_subscriptions
      @notifier.publish "named.subscription", :foo
      @notifier.wait
      assert_equal [["named.subscription", :foo]], @events
      @notifier.unsubscribe("named.subscription")
      @notifier.publish "named.subscription", :foo
      @notifier.wait
      assert_equal [["named.subscription", :foo], ["named.subscription", :foo]], @events
    end

  private
    def event(*args)
      args
    end
  end

  class TestSubscriber
    attr_reader :starts, :finishes, :publishes

    def initialize
      @starts    = []
      @finishes  = []
      @publishes = []
    end

    def start(*args);  @starts << args; end
    def finish(*args); @finishes << args; end
    def publish(*args); @publishes << args; end
  end

  class SyncPubSubTest < TestCase
    def test_events_are_published_to_a_listener
      @notifier.publish :foo
      @notifier.wait
      assert_equal [[:foo]], @events
    end

    def test_publishing_multiple_times_works
      @notifier.publish :foo
      @notifier.publish :foo
      @notifier.wait
      assert_equal [[:foo], [:foo]], @events
    end

    def test_publishing_after_a_new_subscribe_works
      @notifier.publish :foo
      @notifier.publish :foo

      @notifier.subscribe("not_existent") do |*args|
        @events << ActiveSupport::Notifications::Event.new(*args)
      end

      @notifier.publish :foo
      @notifier.publish :foo
      @notifier.wait

      assert_equal [[:foo]] * 4, @events
    end

    def test_log_subscriber_with_string
      events = []
      @notifier.subscribe("1") { |*args| events << args }

      @notifier.publish "1"
      @notifier.publish "1.a"
      @notifier.publish "a.1"
      @notifier.wait

      assert_equal [["1"]], events
    end

    def test_log_subscriber_with_pattern
      events = []
      @notifier.subscribe(/\d/) { |*args| events << args }

      @notifier.publish "1"
      @notifier.publish "a.1"
      @notifier.publish "1.a"
      @notifier.wait

      assert_equal [["1"], ["a.1"], ["1.a"]], events
    end

    def test_multiple_log_subscribers
      @another = []
      @notifier.subscribe { |*args| @another << args }
      @notifier.publish :foo
      @notifier.wait

      assert_equal [[:foo]], @events
      assert_equal [[:foo]], @another
    end

    def test_publish_with_subscriber
      subscriber = TestSubscriber.new
      @notifier.subscribe nil, subscriber
      @notifier.publish :foo

      assert_equal [[:foo]], subscriber.publishes
    end

    private
      def event(*args)
        args
      end
  end

  class InstrumentationTest < TestCase
    delegate :instrument, to: ActiveSupport::Notifications

    def test_instrument_returns_block_result
      assert_equal 2, instrument(:awesome) { 1 + 1 }
    end

    def test_instrument_yields_the_payload_for_further_modification
      assert_equal 2, instrument(:awesome) { |p| p[:result] = 1 + 1 }
      assert_equal 1, @events.size
      assert_equal :awesome, @events.first.name
      assert_equal Hash[result: 2], @events.first.payload
    end

    def test_instrumenter_exposes_its_id
      assert_equal 20, ActiveSupport::Notifications.instrumenter.id.size
    end

    def test_nested_events_can_be_instrumented
      instrument(:awesome, payload: "notifications") do
        instrument(:wot, payload: "child") do
          1 + 1
        end

        assert_equal 1, @events.size
        assert_equal :wot, @events.first.name
        assert_equal Hash[payload: "child"], @events.first.payload
      end

      assert_equal 2, @events.size
      assert_equal :awesome, @events.last.name
      assert_equal Hash[payload: "notifications"], @events.last.payload
    end

    def test_instrument_publishes_when_exception_is_raised
      begin
        instrument(:awesome, payload: "notifications") do
          raise "FAIL"
        end
      rescue RuntimeError => e
        assert_equal "FAIL", e.message
      end

      assert_equal 1, @events.size
      assert_equal Hash[payload: "notifications",
        exception: ["RuntimeError", "FAIL"], exception_object: e], @events.last.payload
    end

    def test_event_is_pushed_even_without_block
      instrument(:awesome, payload: "notifications")
      assert_equal 1, @events.size
      assert_equal :awesome, @events.last.name
      assert_equal Hash[payload: "notifications"], @events.last.payload
    end
  end

  class EventTest < TestCase
    def test_events_are_initialized_with_details
      time = Time.now
      event = event(:foo, time, time + 0.01, random_id, {})

      assert_equal    :foo, event.name
      assert_equal    time, event.time
      assert_in_delta 10.0, event.duration, 0.00001
    end

    def test_events_consumes_information_given_as_payload
      event = event(:foo, Time.now, Time.now + 1, random_id, payload: :bar)
      assert_equal Hash[payload: :bar], event.payload
    end

    def test_event_is_parent_based_on_children
      time = Time.utc(2009, 01, 01, 0, 0, 1)

      parent    = event(:foo, Time.utc(2009), Time.utc(2009) + 100, random_id, {})
      child     = event(:foo, time, time + 10, random_id, {})
      not_child = event(:foo, time, time + 100, random_id, {})

      parent.children << child

      assert parent.parent_of?(child)
      assert !child.parent_of?(parent)
      assert !parent.parent_of?(not_child)
      assert !not_child.parent_of?(parent)
    end

    private
      def random_id
        @random_id ||= SecureRandom.hex(10)
      end
  end
end