Skip to content

Commit

Permalink
Merge pull request #75 from alpaca-tc/improve-ignore
Browse files Browse the repository at this point in the history
Add support for ignoring single method calls in the trace
  • Loading branch information
alpaca-tc authored Sep 5, 2024
2 parents bb85b95 + ba2773f commit fc326d4
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 55 deletions.
39 changes: 21 additions & 18 deletions lib/diver_down/trace/ignored_method_ids.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,64 @@
module DiverDown
module Trace
class IgnoredMethodIds
VALID_VALUE = %i[
single
all
].freeze

def initialize(ignored_methods)
# Ignore all methods in the module
# Hash{ Module => Boolean }
# Hash{ Module => Symbol }
@ignored_modules = {}

# Ignore all methods in the class
# Hash{ Module => Hash{ Symbol => Boolean } }
# Hash{ Module => Hash{ Symbol => Symbol } }
@ignored_class_method_id = Hash.new { |h, k| h[k] = {} }

# Ignore all methods in the instance
# Hash{ Module => Hash{ Symbol => Boolean } }
# Hash{ Module => Hash{ Symbol => Symbol } }
@ignored_instance_method_id = Hash.new { |h, k| h[k] = {} }

ignored_methods.each do |ignored_method|
ignored_methods.each do |ignored_method, value|
raise ArgumentError, "Invalid value: #{value}. valid values are #{VALID_VALUE}" unless VALID_VALUE.include?(value)

if ignored_method.include?('.')
# instance method
class_name, method_id = ignored_method.split('.')
mod = DiverDown::Helper.constantize(class_name)
@ignored_class_method_id[mod][method_id.to_sym] = true
@ignored_class_method_id[mod][method_id.to_sym] = value
elsif ignored_method.include?('#')
# class method
class_name, method_id = ignored_method.split('#')
mod = DiverDown::Helper.constantize(class_name)
@ignored_instance_method_id[mod][method_id.to_sym] = true
@ignored_instance_method_id[mod][method_id.to_sym] = value
else
# module
mod = DiverDown::Helper.constantize(ignored_method)
@ignored_modules[mod] = true
@ignored_modules[mod] = value
end
end
end

# @param mod [Module]
# @param is_class [Boolean] class is true, instance is false
# @param method_id [Symbol]
# @return [Boolean]
def ignored?(mod, is_class, method_id)
ignored_module?(mod) || ignored_method?(mod, is_class, method_id)
# @return [Symbol, false] VALID_VALUE
def ignored(mod, is_class, method_id)
ignored_module(mod) || ignored_method(mod, is_class, method_id)
end

private

def ignored_module?(mod)
def ignored_module(mod)
unless @ignored_modules.key?(mod)
dig_superclass(mod)
end

@ignored_modules.fetch(mod)
end

def ignored_method?(mod, is_class, method_id)
def ignored_method(mod, is_class, method_id)
store = if is_class
# class methods
@ignored_class_method_id
Expand Down Expand Up @@ -96,10 +103,8 @@ def dig_superclass(mod)
end

# Convert nil to boolean
ignored = !!ignored

stack.each do
@ignored_modules[_1] = ignored
@ignored_modules[_1] = ignored || false
end
end

Expand All @@ -125,10 +130,8 @@ def dig_superclass_method_id(store, mod, method_id)
end

# Convert nil to boolean
ignored = !!ignored

stack.each do
store[_1][method_id] = ignored
store[_1][method_id] = ignored || false
end
end
end
Expand Down
9 changes: 6 additions & 3 deletions lib/diver_down/trace/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,12 @@ def build_trace_point
next
end

if !@ignored_method_ids.nil? && @ignored_method_ids.ignored?(mod, DiverDown::Helper.module?(tp.self), tp.method_id)
# If this method is ignored, the call stack is ignored until the method returns.
call_stack.push(ignored: true)
ignored = @ignored_method_ids.ignored(mod, DiverDown::Helper.module?(tp.self), tp.method_id) if @ignored_method_ids

if ignored
# If ignored is :all, the call stack is ignored until the method returns.
# If ignored is :single, the call stack is ignored only current call.
call_stack.push(ignored: ignored == :all)
next
end

Expand Down
2 changes: 1 addition & 1 deletion lib/diver_down/trace/tracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class << self

# @param module_set [DiverDown::Trace::ModuleSet, Array<Module, String>]
# @param caller_paths [Array<String>, nil] if nil, trace all files
# @param ignored_method_ids [Array<String>]
# @param ignored_method_ids [Hash{ String => Symbol }, nil]
# @param filter_method_id_path [#call, nil] filter method_id.path
# @param module_set [DiverDown::Trace::ModuleSet, nil] for optimization
def initialize(module_set: {}, caller_paths: nil, ignored_method_ids: nil, filter_method_id_path: nil)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"prepare": "husky"
},
"dependencies": {
"@hpcc-js/wasm": "^2.16.2",
"@hpcc-js/wasm": "2.16.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-intersection-observer": "^9.10.3",
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 34 additions & 28 deletions spec/diver_down/trace/ignored_method_ids_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,41 @@

RSpec.describe DiverDown::Trace::IgnoredMethodIds do
describe 'InstanceMethods' do
describe '#ignored?' do
describe '#ignored' do
it 'returns false if ignored_methods are blank' do
stub_const('A', Class.new)

ignored_method = described_class.new([])
ignored_method = described_class.new({})

expect(ignored_method.ignored?(A, true, 'new')).to be(false)
expect(ignored_method.ignored(A, true, 'new')).to be(false)
end

it 'returns true if class is matched' do
it 'returns :all if class is ignored as :all' do
stub_const('A', Class.new)
stub_const('B', Class.new)
stub_const('C', Class.new(A))

ignored_method = described_class.new(
[
'A',
]
'A' => :all
)

expect(ignored_method.ignored?(A, true, :new)).to be(true)
expect(ignored_method.ignored?(B, true, :new)).to be(false)
expect(ignored_method.ignored?(C, true, :new)).to be(true)
expect(ignored_method.ignored(A, true, :new)).to be(:all)
expect(ignored_method.ignored(B, true, :new)).to be(false)
expect(ignored_method.ignored(C, true, :new)).to be(:all)
end

it 'returns :single if class is ignored as :single' do
stub_const('A', Class.new)
stub_const('B', Class.new)
stub_const('C', Class.new(A))

ignored_method = described_class.new(
'A' => :single
)

expect(ignored_method.ignored(A, true, :new)).to be(:single)
expect(ignored_method.ignored(B, true, :new)).to be(false)
expect(ignored_method.ignored(C, true, :new)).to be(:single)
end

it 'does not lookup the ancestors if module given because of the complexity of implementation' do
Expand All @@ -33,14 +45,12 @@
stub_const('C', Module.new.tap { _1.extend(A) })

ignored_method = described_class.new(
[
'A.name',
]
'A.name' => :all
)

expect(ignored_method.ignored?(A, true, :name)).to be(true)
expect(ignored_method.ignored?(B, true, :name)).to be(false)
expect(ignored_method.ignored?(C, true, :name)).to be(false)
expect(ignored_method.ignored(A, true, :name)).to be(:all)
expect(ignored_method.ignored(B, true, :name)).to be(false)
expect(ignored_method.ignored(C, true, :name)).to be(false)
end

it 'returns true if class method is matched' do
Expand All @@ -49,14 +59,12 @@
stub_const('C', Class.new(A))

ignored_method = described_class.new(
[
'A.new',
]
'A.new' => :all
)

expect(ignored_method.ignored?(A, true, :new)).to be(true)
expect(ignored_method.ignored?(B, true, :new)).to be(false)
expect(ignored_method.ignored?(C, true, :new)).to be(true)
expect(ignored_method.ignored(A, true, :new)).to be(:all)
expect(ignored_method.ignored(B, true, :new)).to be(false)
expect(ignored_method.ignored(C, true, :new)).to be(:all)
end

it 'returns true if instance method is matched' do
Expand All @@ -65,14 +73,12 @@
stub_const('C', Class.new(A))

ignored_method = described_class.new(
[
'A#initialize',
]
'A#initialize' => :all
)

expect(ignored_method.ignored?(A, false, :initialize)).to be(true)
expect(ignored_method.ignored?(B, false, :initialize)).to be(false)
expect(ignored_method.ignored?(C, false, :initialize)).to be(true)
expect(ignored_method.ignored(A, false, :initialize)).to be(:all)
expect(ignored_method.ignored(B, false, :initialize)).to be(false)
expect(ignored_method.ignored(C, false, :initialize)).to be(:all)
end
end
end
Expand Down
67 changes: 64 additions & 3 deletions spec/diver_down/trace/tracer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -653,9 +653,9 @@ def self.wrap
it 'traces tracer_ignored_call_stack.rb' do
definition = trace_fixture(
'tracer_ignored_call_stack.rb',
ignored_method_ids: [
'AntipollutionModule::B.class_call',
],
ignored_method_ids: {
'AntipollutionModule::B.class_call' => :all,
},
module_set: {
modules: [
'AntipollutionModule::A',
Expand Down Expand Up @@ -695,6 +695,67 @@ def self.wrap
))
end

it 'traces tracer_ignored_call_stack.rb, single' do
definition = trace_fixture(
'tracer_ignored_single_call_stack.rb',
ignored_method_ids: {
'AntipollutionModule::B.class_call' => :single,
},
module_set: {
modules: [
'AntipollutionModule::A',
'AntipollutionModule::B',
'AntipollutionModule::C',
'AntipollutionModule::D',
],
},
caller_paths: [
fixture_path('tracer_ignored_single_call_stack.rb'),
]
)

expect(definition.to_h).to match(fill_default(
title: 'title',
sources: [
{
source_name: 'AntipollutionModule::A',
dependencies: [
{
source_name: 'AntipollutionModule::C',
method_ids: [
{
name: 'class_call',
context: 'class',
paths: [
match(/tracer_ignored_single_call_stack\.rb:\d+/),
],
},
],
},
],
}, {
source_name: 'AntipollutionModule::C',
dependencies: [
{
source_name: 'AntipollutionModule::D',
method_ids: [
{
name: 'class_call',
context: 'class',
paths: [
match(/tracer_ignored_single_call_stack\.rb:\d+/),
],
},
],
},
],
}, {
source_name: 'AntipollutionModule::D', dependencies: [],
},
]
))
end

it 'traces tracer_deep_stack.rb' do
definition = trace_fixture(
'tracer_deep_stack.rb',
Expand Down
27 changes: 27 additions & 0 deletions spec/fixtures/tracer_ignored_single_call_stack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
def run
A.class_call
end

class A
def self.class_call
B.class_call
end
end

class B
def self.class_call
C.class_call
end
end

class C
def self.class_call
D.class_call
end
end

class D
def self.class_call
nil
end
end

0 comments on commit fc326d4

Please sign in to comment.