class ChaosDetector::Navigator

Constants

DEFAULT_GROUP
FRAME_ACTIONS
REGEX_MODULE_UNDECORATE

Attributes

domain_hash[R]
edges[R]
mod_edges[R]
mod_nodes[R]
nodes[R]
options[R]
walkman[R]

Public Class Methods

new(options:) click to toggle source
# File lib/chaos_detector/navigator.rb, line 31
def initialize(options:)
  raise ArgumentError, '#initialize requires options' if options.nil?

  @options = options
  apply_options
end

Public Instance Methods

playback(row_range: nil) click to toggle source

Playback of walkman CSV file:

# File lib/chaos_detector/navigator.rb, line 39
def playback(row_range: nil)
  log('Chaos playing through navigator.  Total lines: ', object: @walkman.count)

  @nodes = Set.new
  @edges_call = Set.new
  @edges_ret = Set.new
  @mod_nodes = Set.new
  @mod_edges = Set.new
  @err_nodes = Set.new

  @walkman.playback(row_range: row_range) do |_rownum, frame|
    perform_node_action(frame)
  end
  # log('Found nodes.', object: @nodes.length)

  @walkman.playback(row_range: row_range) do |_rownum, frame|
    if [:call, :return].include?(frame.event)
      perform_edge_action(frame)
    else
      perform_mod_edge_action(frame)
    end
  end

  edges = merge_edges.to_a
  # log('Found edges.', object: edges.length)

  @mod_graph = ChaosDetector::GraphTheory::Graph.new(
    root_node: ChaosDetector::ChaosGraphs::ModuleNode.root_node(force_new: true),
    nodes: @mod_nodes.to_a,
    edges: @mod_edges.to_a
  )

  @fn_graph = ChaosDetector::GraphTheory::Graph.new(
    root_node: ChaosDetector::ChaosGraphs::FunctionNode.root_node(force_new: true),
    nodes: @nodes.to_a,
    edges: edges
  )

  [@fn_graph, @mod_graph]
end

Private Instance Methods

apply_options() click to toggle source
# File lib/chaos_detector/navigator.rb, line 82
def apply_options
  @walkman = ChaosDetector::Walkman.new(options: @options)
  @domain_hash = {}
  # @options.path_domain_hash && options.path_domain_hash.each do |path, group|
  #   dpath = Pathname.new(path.to_s).cleanpath.to_s
  #   @domain_hash[dpath] = group
  # end
end
domain_from_path(local_path:) click to toggle source
# File lib/chaos_detector/navigator.rb, line 228
def domain_from_path(local_path:)
  key = domain_hash.keys.find { |k| local_path.start_with?(k) }
  key ? domain_hash[key] : ChaosDetector::GraphTheory::Node::ROOT_NODE_NAME
end
edge_for_nodes(src_node, dep_node, edges:, edge_type: :dependent) click to toggle source
# File lib/chaos_detector/navigator.rb, line 172
def edge_for_nodes(src_node, dep_node, edges:, edge_type: :dependent)
  edge = edges.find do |e|
    e.src_node == src_node && e.dep_node == dep_node
  end
  if edge.nil?
    edge = ChaosDetector::GraphTheory::Edge.new(src_node, dep_node, edge_type: edge_type)
    edges << edge
  end
  edge
end
fn_node_for(fn_info) click to toggle source
# File lib/chaos_detector/navigator.rb, line 116
def fn_node_for(fn_info)
  return nil unless fn_info&.fn_name

  @nodes.find do |n|
    n.fn_name == fn_info.fn_name &&
      n.fn_path == fn_info.fn_path
  end
end
fn_node_for_frame(frame) click to toggle source

@return Node matching given frame or create a new one.

# File lib/chaos_detector/navigator.rb, line 135
def fn_node_for_frame(frame)
  # log("Calling fn_node_for_frame", object: frame)
  node = fn_node_for(frame.fn_info)

  if node.nil? && frame.event == :call
    fn_info = frame.fn_info
    node = ChaosDetector::ChaosGraphs::FunctionNode.new(
      fn_name: fn_info.fn_name,
      fn_path: fn_info.fn_path,
      fn_line: fn_info.fn_line,
      domain_name: options.domain_from_path(fn_info.fn_path),
      mod_info: frame.mod_info
    )
    @nodes << node
  end

  node
end
log(msg, **opts) click to toggle source
# File lib/chaos_detector/navigator.rb, line 233
def log(msg, **opts)
  ChaosUtils.log_msg(msg, subject: 'Navigator', **opts)
end
merge_edges() click to toggle source

We merge/reduce edges elsewhere:

# File lib/chaos_detector/navigator.rb, line 92
def merge_edges
  c = Set.new(@edges_call)
  r = Set.new(@edges_ret)

  raise 'Call Edges should be Set' unless c.length == @edges_call.length
  raise 'Ret Edges should be Set' unless r.length == @edges_ret.length

  raise 'Call Edges should be unique' unless @edges_call.uniq.length == @edges_call.length
  raise 'Call Edges should be unique' unless @edges_ret.uniq.length == @edges_ret.length

  # log('Unique edges in call (n/total)', object: [(c - r).length, c.length])
  # log('Unique edges in return (n/total)', object: [(r - c).length, r.length])

  # @edges_call.each do |e|
  #   log("edges_call", object: e)
  # end

  # @edges_ret.each do |e|
  #   log("edges_ret ", object: e)
  # end

  c.union(r)
end
mod_node_for(mod_info) click to toggle source
# File lib/chaos_detector/navigator.rb, line 125
def mod_node_for(mod_info)
  return nil unless mod_info&.mod_name

  @mod_nodes.find do |n|
    n.mod_name == mod_info.mod_name &&
      n.mod_path == mod_info.mod_path
  end
end
mod_node_from_info(mod_info) click to toggle source
# File lib/chaos_detector/navigator.rb, line 154
def mod_node_from_info(mod_info)
  # log("Calling fn_node_for_frame", object: frame)
  node = mod_node_for(mod_info)

  if node.nil? #&& frame.event == :call
    node = ChaosDetector::ChaosGraphs::ModuleNode.new(
      mod_name: mod_info.mod_name,
      mod_path: mod_info.mod_path,
      mod_type: mod_info.mod_type,
      domain_name: options.domain_from_path(mod_info.mod_path)
    )

    @mod_nodes << node
  end

  node
end
perform_edge_action(frame) click to toggle source
# File lib/chaos_detector/navigator.rb, line 195
def perform_edge_action(frame)
  return unless frame.fn_info && frame.event==:call #&& frame.caller_info

  dest_node = fn_node_for(frame.fn_info)
  if dest_node.nil?
    # unless @err_nodes.include?(dest_node)
    #   log "Couldn't find destination node (of #{@nodes.length} / #{@edges_call.length}) on #{frame}"
    #   @err_nodes << dest_node
    # end
    return
  end

  caller_node = fn_node_for(frame.caller_info)
  if caller_node.nil?
    caller_node = ChaosDetector::ChaosGraphs::FunctionNode.root_node
    raise 'Caller node is required (falls back to root).' if caller_node.nil?

    # log("Adding edge to root!")
    @nodes << caller_node
  end

  edges = frame.event == :return ? @edges_ret : @edges_call
  edge_for_nodes(caller_node, dest_node, edges: edges)
end
perform_mod_edge_action(frame) click to toggle source
# File lib/chaos_detector/navigator.rb, line 220
def perform_mod_edge_action(frame)
  return unless frame.mod_info && frame.caller_info

  caller_node = mod_node_from_info(frame.caller_info)
  dest_node = mod_node_from_info(frame.mod_info)
  edge_for_nodes(dest_node, caller_node, edges: @mod_edges, edge_type: frame.event)
end
perform_node_action(frame) click to toggle source
# File lib/chaos_detector/navigator.rb, line 183
def perform_node_action(frame)
  return unless [:call, :return].include?(frame.event)
  node = fn_node_for_frame(frame)

  ChaosUtils.with(node && frame.event == :return && frame.fn_info.fn_line) do |fn_line|
    if !node.fn_line_end.nil? && node.fn_line_end != fn_line
    end
    node.fn_line_end = [fn_line, node.fn_line_end.to_i].max
  end
end