Skip to content
This repository has been archived by the owner on May 4, 2024. It is now read-only.

Support TappingDevice::Device#stop_when #4

Merged
merged 2 commits into from
Nov 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions lib/tapping_device/device.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
require "active_record"
require "tapping_device/exceptions"

module TappingDevice
class Device
CALLER_START_POINT = 2
FORCE_STOP_WHEN_MESSAGE = "You must set stop_when condition before start tapping"

attr_reader :options, :calls

Expand All @@ -26,6 +28,21 @@ def tap_assoc!(record)
track(record, condition: :tap_associations?, block: @block, **@options)
end

def tap_init(klass)
validate_tapping(__method__)
tap_init!(klass)
end

def tap_on(object)
validate_tapping(__method__)
tap_on!(object)
end

def tap_assoc(record)
validate_tapping(__method__)
tap_assoc!(record)
end

def set_block(&block)
@block = block
end
Expand All @@ -34,9 +51,24 @@ def stop!
@trace_point.disable if @trace_point
end

def stop_when(&block)
@stop_when = block
end

private

def validate_tapping(method_name)
unless @stop_when
raise TappingDevice::Exception.new <<~ERROR
You must set stop_when condition before calling #{method_name}. Or you can use #{method_name}! to force tapping.
Tapping without stop condition can largely slow down or even halt your application, because it'll need to
screen literally every call happened.
ERROR
end
end

def track(object, condition:, block:, with_trace_to: nil, exclude_by_paths: [], filter_by_paths: nil)

@trace_point = TracePoint.trace(:return) do |tp|
filepath, line_number = caller(CALLER_START_POINT).first.split(":")[0..1]

Expand Down Expand Up @@ -70,6 +102,8 @@ def track(object, condition:, block:, with_trace_to: nil, exclude_by_paths: [],
@calls << yield_parameters
end
end

stop! if @stop_when&.call(yield_parameters)
end

self
Expand Down
4 changes: 4 additions & 0 deletions lib/tapping_device/exceptions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module TappingDevice
class Exception < StandardError
end
end
51 changes: 51 additions & 0 deletions spec/performance_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
require "spec_helper"
require 'benchmark'

RSpec.describe TappingDevice::Device do
let(:devices) do
devices = []
100.times { devices << TappingDevice::Device.new }
devices
end

after do
devices.each(&:stop!)
end

context "without stop_when" do
it "takes long time when tapping multiple devices" do
time = Benchmark.realtime do
devices.each do |device|
s = Student.new("foo", 10)
device.tap_on!(s)
10.times { s.name }

expect(device.calls.count).to eq(10)
end
end
devices.each { |d| d.stop! }
expect(time).to be_between(4, 10)
end
end

context "with stop_when" do
it "takes very short time when tapping multiple devices" do
time = Benchmark.realtime do
devices.each do |device|
device.stop_when do
device.calls.count == 10
end

s = Student.new("foo", 10)
device.tap_on!(s)
10.times { s.name }

expect(device.calls.count).to eq(10)
end
end

devices.each { |d| d.stop! }
expect(time).to be_between(0, 1)
end
end
end
45 changes: 45 additions & 0 deletions spec/tapping_device_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,49 @@
expect(count).to eq(1)
end
end

describe "#tap_init" do
let(:device) { TappingDevice::Device.new }
let(:stan) { Student.new("stan", 25) }

it "raises error if device has no stop_when set" do
expect { device.tap_init(Student) }.to raise_error(TappingDevice::Exception)
end
end

describe "#tap_on" do
let(:device) { TappingDevice::Device.new }
let(:stan) { Student.new("stan", 25) }

it "raises error if device has no stop_when set" do
expect { device.tap_on(stan) }.to raise_error(TappingDevice::Exception)
end
end

describe "#tap_assoc" do
let(:device) { TappingDevice::Device.new }
let(:post) { Post.new }

it "raises error if device has no stop_when set" do
expect { device.tap_assoc(post) }.to raise_error(TappingDevice::Exception)
end
end

describe "#stop_when" do
it "stops tapping once fulfill stop_when condition" do
device = TappingDevice::Device.new
device.stop_when do |payload|
device.calls.count == 10
end

s = Student.new("foo#", 10)
device.tap_on!(s)

100.times do
s.name
end

expect(device.calls.count).to eq(10)
end
end
end