diff options
Diffstat (limited to 'activesupport/test/notifications_test.rb')
-rw-r--r-- | activesupport/test/notifications_test.rb | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb new file mode 100644 index 0000000000..4e0aef2cc7 --- /dev/null +++ b/activesupport/test/notifications_test.rb @@ -0,0 +1,319 @@ +# 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 SubscribeEventObjectsTest < TestCase + def test_subscribe_events + events = [] + @notifier.subscribe do |event| + events << event + end + + ActiveSupport::Notifications.instrument("foo") + event = events.first + assert event, "should have an event" + assert_operator event.allocations, :>, 0 + assert_operator event.cpu_time, :>, 0 + assert_operator event.idle_time, :>, 0 + assert_operator event.duration, :>, 0 + end + + def test_subscribe_via_top_level_api + old_notifier = ActiveSupport::Notifications.notifier + ActiveSupport::Notifications.notifier = ActiveSupport::Notifications::Fanout.new + + event = nil + ActiveSupport::Notifications.subscribe("foo") do |e| + event = e + end + + ActiveSupport::Notifications.instrument("foo") do + 100.times { Object.new } # allocate at least 100 objects + end + + assert event + assert_operator event.allocations, :>=, 100 + ensure + ActiveSupport::Notifications.notifier = old_notifier + 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_not child.parent_of?(parent) + assert_not parent.parent_of?(not_child) + assert_not not_child.parent_of?(parent) + end + + private + def random_id + @random_id ||= SecureRandom.hex(10) + end + end +end |