trace2 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4f7e6874b292e5edcc36ba88b1f9f13798765d1682852a64b0be5667807a3b15
4
+ data.tar.gz: a3548111fdb9a4efb25f4303df014a9d3e1dc974f0dae15a517f2bafe505e4da
5
+ SHA512:
6
+ metadata.gz: 35d7b742f6f9908f9ccc168551f8e7318bcde9d710cac0cb2e5eb1ffbccae0cf93ea4e1b3634e335f1bae412c2f1b0914f84c93cef4a94b0f2fd66e58cca5e6b
7
+ data.tar.gz: efb0c1e6c3a1fa059ee6d4ae4aa0a528e374f34771d6502782712196f6bfa25c30afdda57c1eecf730690b08ae57d1084325a4d8bff6e568962490a88d7e3f8b
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'trace2'
5
+
6
+ Trace2::Runner.run
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+
3
+ create_header
4
+ create_makefile 'trace2/trace2'
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ RUBY_VERSION =~ /(\d+\.\d+)/
5
+ require "trace2/#{Regexp.last_match(1)}/trace2"
6
+ rescue LoadError
7
+ require 'trace2/trace2'
8
+ end
9
+
10
+ require 'trace2/class_lister'
11
+ require 'trace2/class_use'
12
+ require 'trace2/class_use_factory'
13
+ require 'trace2/event_processor'
14
+ require 'trace2/executable_runner'
15
+ require 'trace2/filter_parser'
16
+ require 'trace2/option_parser'
17
+ require 'trace2/options'
18
+ require 'trace2/query_use'
19
+ require 'trace2/runner'
20
+ require 'trace2/version'
21
+ require 'trace2/graph_generator'
22
+ require 'trace2/reporting_tools_factory'
23
+ require 'trace2/dot_wrapper'
Binary file
Binary file
Binary file
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Trace2
6
+ # Responsable for listing all accessed classes
7
+ # along with their dependencies
8
+ class ClassLister
9
+ extend Forwardable
10
+
11
+ attr_accessor :classes_uses
12
+
13
+ def_delegators :@trace_point, :enable
14
+
15
+ def initialize(event_processor, trace_point = TracePoint)
16
+ @event_processor = event_processor
17
+ @classes_uses = []
18
+ @trace_point = trace_point.new(*@event_processor.events) do |tp|
19
+ @event_processor.process_event(tp)
20
+ end
21
+ end
22
+
23
+ def disable
24
+ @trace_point.disable
25
+ @event_processor.aggregate_uses
26
+ @classes_uses = @event_processor.classes_uses
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ # Registers how a class was used during run time
5
+ class ClassUse
6
+ attr_reader :name, :method, :stack_level, :path, :line, :callees, :event
7
+ attr_accessor :caller_class
8
+
9
+ def initialize(params)
10
+ @name = params[:name]
11
+ @method = params[:method]
12
+ @caller_class = params[:caller_class]
13
+ @stack_level = params[:stack_level]
14
+ @path = params[:path]
15
+ @line = params[:line]
16
+ @callees = params[:callees] || []
17
+ @event = params[:event]
18
+ end
19
+
20
+ def callers_stack(options = {})
21
+ curr_class = caller_class
22
+ callers_stack = []
23
+ until curr_class.nil?
24
+ curr_caller = run_options(curr_class, options)
25
+ callers_stack.push(curr_caller) unless curr_caller.nil?
26
+ curr_class = curr_class.caller_class
27
+ end
28
+ callers_stack
29
+ end
30
+
31
+ def matches_method?(methods_names)
32
+ methods_names.any? { |method_name| method.match(method_name) }
33
+ end
34
+
35
+ def matches_name?(classes_names)
36
+ classes_names.any? { |class_name| name.match(class_name) }
37
+ end
38
+
39
+ def matches_path?(paths_patterns)
40
+ paths_patterns.any? { |path_pattern| path.match(path_pattern) }
41
+ end
42
+
43
+ def matches_bottom_of_stack?(is_bottom)
44
+ caller_class.nil? == is_bottom
45
+ end
46
+
47
+ def matches_top_of_stack?(is_top)
48
+ callees.empty? == is_top
49
+ end
50
+
51
+ def matches_caller_class?(caller_attributes)
52
+ callers_stack.any? do |current_caller|
53
+ valid_caller?(current_caller, caller_attributes)
54
+ end
55
+ end
56
+
57
+ def add_callee(callee)
58
+ callees << callee
59
+ end
60
+
61
+ private
62
+
63
+ def run_options(class_use, options)
64
+ options.reduce(class_use) do |acc_use, option|
65
+ option_method = "run_#{option.first}"
66
+ send(option_method, acc_use, option.last) unless acc_use.nil?
67
+ end
68
+ end
69
+
70
+ def run_selector(class_use, selector)
71
+ return class_use if selector.nil?
72
+
73
+ selector.filter(class_use)
74
+ end
75
+
76
+ def run_compact(class_use, compact)
77
+ compact ? compact_callers(class_use) : class_use
78
+ end
79
+
80
+ def compact_callers(class_use)
81
+ class_use.dup.tap do |compacted_use|
82
+ compacted_use.caller_class = nil
83
+ end
84
+ end
85
+
86
+ def valid_caller?(current_caller, caller_attributes)
87
+ caller_attributes.all? do |attribute, values|
88
+ validation = "matches_#{attribute}?"
89
+ current_caller.send(validation, values)
90
+ end
91
+ end
92
+
93
+ def respond_to_missing?(method, _)
94
+ method.match?(/^matches_\S+?\?$/)
95
+ end
96
+
97
+ def method_missing(method, *args, &block)
98
+ return true if respond_to_missing?(method, args)
99
+
100
+ super
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ # Builds a ClassUse from TracePoint
5
+ class ClassUseFactory
6
+ CLASS_POINTER_FORMAT = '0x[0-9abcdef]+'
7
+
8
+ def self.build(trace_point:, caller_class:, stack_level:)
9
+ ClassUse.new(
10
+ trace_point_params(trace_point)
11
+ .merge(caller_class: caller_class)
12
+ .merge(stack_level: stack_level)
13
+ )
14
+ end
15
+
16
+ def self.class_name(trace_point)
17
+ Trace2::NameFinder.class_name(trace_point.self)
18
+ end
19
+
20
+ class << self
21
+ def trace_point_params(trace_point)
22
+ {
23
+ name: class_name(trace_point),
24
+ method: trace_point.callee_id.to_s,
25
+ path: trace_point.path,
26
+ line: trace_point.lineno,
27
+ event: trace_point.event
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ # A class to abstract the usage of the dot executable
5
+ # that comes with graphviz
6
+ class DotWrapper
7
+ def initialize(kernel: Kernel)
8
+ @kernel = kernel
9
+ end
10
+
11
+ def render_graph(input_path, output_path, format)
12
+ return warn_graphviz_not_installed unless graphviz_installed?
13
+
14
+ execute_graph_render(input_path, output_path, format)
15
+ end
16
+
17
+ private
18
+
19
+ DOT_VERSION = 'dot -V'
20
+ DOT_RENDER = 'dot %s -T%s -o %s'
21
+
22
+ def graphviz_installed?
23
+ @kernel.system(DOT_VERSION)
24
+ end
25
+
26
+ def warn_graphviz_not_installed
27
+ @kernel.puts 'Graphviz is not installed on the system. '\
28
+ 'Skipping graph rendering...'
29
+
30
+ false
31
+ end
32
+
33
+ def execute_graph_render(input_path, output_path, format)
34
+ graph_render_command = format(
35
+ DOT_RENDER, input_path, format, output_path
36
+ )
37
+ @kernel.system(graph_render_command)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ # Processes a TracePoint event, generating an array
5
+ # of ClassUse
6
+ class EventProcessor
7
+ attr_accessor :classes_uses
8
+ EVENTS = %i[call b_call].freeze
9
+
10
+ def initialize(filter_by, kernel: Kernel, query_use: QueryUse,
11
+ class_use_factory: ClassUseFactory)
12
+ @callers_stack = []
13
+ @classes_uses = []
14
+ @class_use_factory = class_use_factory
15
+ @kernel = kernel
16
+ @selector = query_use.where(filter_by)
17
+ @stack_level = @kernel.caller.length
18
+ end
19
+
20
+ def aggregate_uses
21
+ @classes_uses = @callers_stack
22
+ .map { |caller_class| @selector.filter(caller_class) }
23
+ .concat(@classes_uses)
24
+ .reject(&:nil?)
25
+ @callers_stack = []
26
+ end
27
+
28
+ def process_event(trace_point)
29
+ @stack_level = @kernel.caller.length
30
+
31
+ remove_exited_callers_from_stack
32
+ current_class_use = build_class_use(trace_point, current_caller)
33
+ update_top_of_stack(current_class_use)
34
+
35
+ @callers_stack.unshift(current_class_use)
36
+ end
37
+
38
+ def events
39
+ EVENTS
40
+ end
41
+
42
+ private
43
+
44
+ def remove_exited_callers_from_stack
45
+ while class_exited?(@callers_stack.first)
46
+ @classes_uses << @selector.filter(@callers_stack.shift)
47
+ end
48
+ end
49
+
50
+ def build_class_use(trace_point, caller_class)
51
+ @class_use_factory.build(
52
+ trace_point: trace_point,
53
+ stack_level: @stack_level,
54
+ caller_class: caller_class
55
+ )
56
+ end
57
+
58
+ def update_top_of_stack(callee)
59
+ caller_class = callee.caller_class
60
+ return if caller_class.nil?
61
+
62
+ caller_class.add_callee(callee) unless @selector.filter(callee).nil?
63
+ end
64
+
65
+ def class_exited?(current_class)
66
+ current_class && current_class.stack_level >= @stack_level
67
+ end
68
+
69
+ def current_caller
70
+ @callers_stack.drop_while do |possible_caller|
71
+ possible_caller.nil? || @selector.filter(possible_caller).nil?
72
+ end.first
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ # Class that runs the executable whose runtime will be
5
+ # used to analyze the relationship between classes
6
+ class ExecutableRunner
7
+ def initialize(system_path: ENV['PATH'], argv: ARGV)
8
+ @system_path = system_path
9
+ @argv = argv
10
+ end
11
+
12
+ def run(executable, args)
13
+ update_argv(args)
14
+ executable_path = find_executable(executable)
15
+ load(executable_path)
16
+ rescue SyntaxError
17
+ raise SyntaxError, "#{executable} is not a valid Ruby script"
18
+ end
19
+
20
+ private
21
+
22
+ def find_executable(executable)
23
+ possible_paths = @system_path.split(':').unshift('.').map do |path|
24
+ "#{path}/#{executable}"
25
+ end
26
+
27
+ executable_path = possible_paths.find do |path|
28
+ File.exist?(path)
29
+ end
30
+
31
+ if executable_path.nil?
32
+ raise ArgumentError, "#{executable} does not exist"
33
+ end
34
+
35
+ executable_path
36
+ end
37
+
38
+ def update_argv(args)
39
+ @argv.clear.concat(args)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ # Class that queries ClassUse by parameters
5
+ # passed as a hash
6
+ class FilterParser
7
+ FILTER = 'filter'
8
+ VALIDATE = 'validate_%s'
9
+ PARSE = 'parse_%s'
10
+
11
+ def parse(filters)
12
+ @filters = filters
13
+ @parsed_filter = [filters.length]
14
+ parse_filters
15
+ @parsed_filter.map(&:to_s)
16
+ end
17
+
18
+ private
19
+
20
+ def parse_filters
21
+ @filters.each do |filter|
22
+ @parsed_filter.push(filter.length)
23
+ filter.each do |action, validations|
24
+ parse_validations(validations)
25
+ @parsed_filter.push(action.to_s)
26
+ end
27
+ @parsed_filter.push(FILTER)
28
+ end
29
+ end
30
+
31
+ def parse_validations(validations)
32
+ @parsed_filter.push(validations.length)
33
+ validations.each do |validation|
34
+ @parsed_filter.push(validation.length)
35
+ parse_validation(validation)
36
+ end
37
+ end
38
+
39
+ def parse_validation(validation)
40
+ validation.each do |attribute, values|
41
+ @parsed_filter.push(format(VALIDATE, attribute))
42
+ current_parser = parse_values_method(values)
43
+ if respond_to?(current_parser, true)
44
+ send(current_parser, values)
45
+ else
46
+ @parsed_filter.push(1)
47
+ @parsed_filter.push(values.to_s)
48
+ end
49
+ end
50
+ end
51
+
52
+ def parse_values_method(values)
53
+ format(PARSE, values.class.to_s.downcase)
54
+ end
55
+
56
+ def parse_array(values)
57
+ @parsed_filter.push(values.length)
58
+ @parsed_filter.concat(values)
59
+ end
60
+
61
+ def parse_hash(values)
62
+ @parsed_filter.push(values.keys.length)
63
+ parse_validation(values)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ # Generates a graph in .dot format from an array of
5
+ # ClassUse
6
+ class GraphGenerator
7
+ def run(output_path, class_lister)
8
+ parsed_classes = parse_classes(class_lister.classes_uses)
9
+ File.write(output_path, base_graph(parsed_classes))
10
+ end
11
+
12
+ private
13
+
14
+ def base_graph(parsed_classes)
15
+ <<~FILE
16
+ digraph {
17
+ #{parsed_classes}
18
+ }
19
+ FILE
20
+ end
21
+
22
+ def parse_classes(classes_uses)
23
+ classes_uses.flat_map do |class_use|
24
+ caller_relationship(class_use) + callee_relationships(class_use)
25
+ end.uniq.join("\n")
26
+ end
27
+
28
+ def caller_relationship(class_use)
29
+ return [] unless class_use.caller_class
30
+
31
+ [to_graph(class_use.caller_class, class_use)]
32
+ end
33
+
34
+ def callee_relationships(class_use)
35
+ class_use.callees.map { |callee| to_graph(class_use, callee) }
36
+ end
37
+
38
+ def to_graph(caller_class, callee)
39
+ "\s\s\"#{caller_class.name}\" -> \"#{callee.name}\""
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module Trace2
6
+ # Class that parses the options that will be used
7
+ # by trace2
8
+ class OptionParser < ::OptionParser
9
+ attr_reader :options_keys
10
+
11
+ def initialize(banner = nil, width = 32, indent = ' ' * 4)
12
+ @options_keys = {}
13
+ super(banner, width, indent)
14
+ end
15
+
16
+ def add_option(short: nil, long: nil, description: [])
17
+ @options_keys.merge!(**option_hash(short), **option_hash(long))
18
+ options = [short, long].compact
19
+ on(*options, *description) do |option_value|
20
+ yield option_value
21
+ end
22
+ end
23
+
24
+ def split_executables(args)
25
+ second_executable = second_executable_arguments(args)
26
+
27
+ [args.shift(args.length - second_executable.length), second_executable]
28
+ end
29
+
30
+ private
31
+
32
+ def second_executable_arguments(args)
33
+ argument = false
34
+ args.drop_while do |arg|
35
+ accepts_argument = @options_keys[arg.to_sym]
36
+ if accepts_argument.nil? && !argument
37
+ false
38
+ else
39
+ argument = accepts_argument
40
+ true
41
+ end
42
+ end
43
+ end
44
+
45
+ def option_hash(option)
46
+ return {} if option.nil?
47
+
48
+ arguments = option.split
49
+ option_name = arguments.first
50
+ has_arguments = arguments.length > 1
51
+ { option_name.to_sym => has_arguments }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ # Defines and returns options for Trace2's runner,
5
+ # given the arguments that were passed
6
+ # rubocop:disable Metrics/ClassLength
7
+ class Options
8
+ def self.parse(args)
9
+ new.parse(args)
10
+ end
11
+
12
+ # rubocop:disable Metrics/MethodLength
13
+ def initialize(option_parser: Trace2::OptionParser.new, kernel: Kernel)
14
+ @options = default_options
15
+ @kernel = kernel
16
+ @option_parser = option_parser
17
+ options_banner
18
+ help_option
19
+ version_option
20
+ filter_option
21
+ output_path_option
22
+ type_option
23
+ format_option
24
+ manual_option
25
+ end
26
+ # rubocop:enable Metrics/MethodLength
27
+
28
+ def parse(args)
29
+ trace2, executable_args = @option_parser.split_executables(args)
30
+ executable = executable_args.shift
31
+
32
+ @option_parser.parse(trace2)
33
+
34
+ raise_missing_executable if executable.nil?
35
+
36
+ @options.merge(executable: executable, args: executable_args)
37
+ end
38
+
39
+ private
40
+
41
+ def default_options
42
+ {
43
+ tools_type: :native,
44
+ automatic_render: true,
45
+ graph_format: 'pdf',
46
+ output_path: 'trace2_report.dot',
47
+ filter_path: '.trace2.yml'
48
+ }
49
+ end
50
+
51
+ def options_banner
52
+ @option_parser.banner = 'Usage: trace2 [options] ' \
53
+ 'RUBY_EXECUTABLE [executable options]'
54
+ end
55
+
56
+ def help_option
57
+ @option_parser.add_option(short: '-h',
58
+ long: '--help',
59
+ description: 'Display help') do
60
+ puts @option_parser.to_s
61
+ exit_runner
62
+ end
63
+ end
64
+
65
+ def version_option
66
+ @option_parser.add_option(short: '-v',
67
+ long: '--version',
68
+ description: 'Show trace2 version') do
69
+ puts VERSION
70
+ exit_runner
71
+ end
72
+ end
73
+
74
+ def filter_option
75
+ filter_description = 'Specify a filter file. Defaults to .trace2.yml'
76
+ @option_parser.add_option(description: filter_description,
77
+ long: '--filter FILTER_PATH') do |filter|
78
+ @options[:filter_path] = filter
79
+ end
80
+ end
81
+
82
+ def output_path_option
83
+ output_path = ['Output path for the report file. Defaults to',
84
+ './trace2_report.yml']
85
+ @option_parser.add_option(short: '-o OUTPUT_PATH',
86
+ description: output_path,
87
+ long: '--output OUTPUT_PATH') do |output|
88
+ @options[:output_path] = output
89
+ end
90
+ end
91
+
92
+ def type_option
93
+ tools_type = ['Type of the tools that will be used to generate the',
94
+ 'relationship between classes. Possible values:',
95
+ 'ruby or native. Defaults to native.']
96
+
97
+ @option_parser.add_option(short: '-t TOOLS_TYPE',
98
+ description: tools_type,
99
+ long: '--type TOOLS_TYPE') do |type|
100
+ @options[:tools_type] = type.to_sym
101
+ end
102
+ end
103
+
104
+ def manual_option
105
+ run_manually = 'Don\'t try to render the relationships ' \
106
+ 'graph automatically'
107
+ @option_parser.add_option(short: '-m',
108
+ description: run_manually,
109
+ long: '--manual') do
110
+ @options[:automatic_render] = false
111
+ end
112
+ end
113
+
114
+ def format_option
115
+ output_format = ['Format that will be used to render the relationship\'s',
116
+ 'graph. Has no effect if the manual option is set.',
117
+ 'Defaults to pdf.']
118
+ @option_parser.add_option(long: '--format FORMAT',
119
+ description: output_format) do |format|
120
+ @options[:graph_format] = format
121
+ end
122
+ end
123
+
124
+ def raise_missing_executable
125
+ raise ArgumentError, 'an executable or ruby script name'\
126
+ ' must be passed as argument'
127
+ end
128
+
129
+ def exit_runner
130
+ @kernel.exit
131
+ end
132
+ end
133
+ # rubocop:enable Metrics/ClassLength
134
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ # Class that queries ClassUse by parameters
5
+ # passed as a hash
6
+ class QueryUse
7
+ attr_reader :classes_uses
8
+
9
+ def initialize(queries)
10
+ @queries = queries
11
+ end
12
+
13
+ def self.where(queries)
14
+ QueryUse.new(queries)
15
+ end
16
+
17
+ def filter(class_use)
18
+ class_use if class_use && valid_use?(class_use)
19
+ end
20
+
21
+ def select(classes_uses)
22
+ classes_uses.select do |class_use|
23
+ valid_use?(class_use)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def valid_use?(class_use)
30
+ @queries.all? do |query|
31
+ query.all? do |action, validations|
32
+ send(action, matches_validations?(class_use, validations))
33
+ end
34
+ end
35
+ end
36
+
37
+ def allow(query_result)
38
+ query_result
39
+ end
40
+
41
+ def reject(query_result)
42
+ !query_result
43
+ end
44
+
45
+ def matches_validations?(class_use, validations)
46
+ validations.any? do |validation|
47
+ validation.all? do |attribute, values|
48
+ attribute_match = "matches_#{attribute}?"
49
+ class_use.send(attribute_match, values)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ # Builds a ClassLister and GraphGenerator instance depending on the type
5
+ # that is passed.
6
+ class ReportingToolsFactory
7
+ def initialize(class_lister: ClassLister)
8
+ @class_lister = class_lister
9
+ end
10
+
11
+ def build(filter, type: :native)
12
+ @type = type
13
+ {
14
+ class_lister: build_class_lister(filter),
15
+ graph_generator: type_options[:graph_generator]
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ Trace2::NATIVE = {
22
+ event_processor: Trace2::EventProcessorC,
23
+ filter_parser: Trace2::FilterParser.new,
24
+ graph_generator: Trace2::GraphGeneratorC.new
25
+ }.freeze
26
+
27
+ Trace2::RUBY = {
28
+ event_processor: Trace2::EventProcessor,
29
+ filter_parser: nil,
30
+ graph_generator: Trace2::GraphGenerator.new
31
+ }.freeze
32
+
33
+ def build_class_lister(filter)
34
+ parsed_filter = build_filter(filter)
35
+ event_processor = type_options[:event_processor].new(parsed_filter)
36
+ @class_lister.new(event_processor)
37
+ end
38
+
39
+ def build_filter(unparsed_filter)
40
+ filter_parser = type_options[:filter_parser]
41
+ return filter_parser.parse(unparsed_filter) if filter_parser
42
+
43
+ unparsed_filter
44
+ end
45
+
46
+ def type_options
47
+ Object.const_get("Trace2::#{@type.upcase}")
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Trace2
6
+ # Base class for trace2's executable
7
+ class Runner
8
+ def self.run(args: ARGV, options: Options.new)
9
+ options_hash = options.parse(args)
10
+ new(options_hash).run
11
+ end
12
+
13
+ def initialize(options)
14
+ @args = options[:args]
15
+ @executable = options[:executable]
16
+ @output_path = options.fetch(:output_path)
17
+ @executable_runner = options[:executable_runner] || ExecutableRunner.new
18
+ @render_graph_automatically = options.fetch(:automatic_render, false)
19
+ @graph_format = options[:graph_format]
20
+ @filter_path = options[:filter_path]
21
+ @dot_wrapper = options.fetch(:dot_wrapper, DotWrapper.new)
22
+ build_class_lister(options)
23
+ end
24
+
25
+ def run
26
+ at_exit { end_class_listing }
27
+ class_lister.enable
28
+ executable_runner.run(executable, args)
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :args, :class_lister, :executable, :executable_runner,
34
+ :output_path, :graph_generator, :render_graph_automatically,
35
+ :dot_wrapper
36
+
37
+ def build_class_lister(options)
38
+ filter = load_filter
39
+ tools = options.fetch(:reporting_tools_factory, ReportingToolsFactory.new)
40
+ .build(filter, type: options[:tools_type])
41
+
42
+ @class_lister = tools[:class_lister]
43
+ @graph_generator = tools[:graph_generator]
44
+ end
45
+
46
+ def load_filter
47
+ return YAML.load_file(@filter_path) if File.exist?(@filter_path)
48
+
49
+ []
50
+ end
51
+
52
+ def end_class_listing
53
+ class_lister.disable
54
+ graph_generator.run(output_path, class_lister)
55
+ run_graph_rendering
56
+ end
57
+
58
+ def run_graph_rendering
59
+ return unless render_graph_automatically
60
+
61
+ final_file = "#{output_path}.#{@graph_format}"
62
+ dot_wrapper.render_graph(output_path, final_file, @graph_format)
63
+ end
64
+ end
65
+ end
Binary file
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trace2
4
+ VERSION = '1.0.1'
5
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trace2
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Raphael Montani
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-09-27 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Check the runtime dependencies of your classes
14
+ email: raphael.muntani@gmail.com
15
+ executables:
16
+ - trace2
17
+ extensions:
18
+ - ext/trace2/extconf.rb
19
+ extra_rdoc_files: []
20
+ files:
21
+ - bin/trace2
22
+ - ext/trace2/extconf.rb
23
+ - lib/trace2.rb
24
+ - lib/trace2/2.5/trace2.so
25
+ - lib/trace2/2.6/trace2.so
26
+ - lib/trace2/2.7/trace2.so
27
+ - lib/trace2/class_lister.rb
28
+ - lib/trace2/class_use.rb
29
+ - lib/trace2/class_use_factory.rb
30
+ - lib/trace2/dot_wrapper.rb
31
+ - lib/trace2/event_processor.rb
32
+ - lib/trace2/executable_runner.rb
33
+ - lib/trace2/filter_parser.rb
34
+ - lib/trace2/graph_generator.rb
35
+ - lib/trace2/option_parser.rb
36
+ - lib/trace2/options.rb
37
+ - lib/trace2/query_use.rb
38
+ - lib/trace2/reporting_tools_factory.rb
39
+ - lib/trace2/runner.rb
40
+ - lib/trace2/trace2.so
41
+ - lib/trace2/version.rb
42
+ homepage: https://github.com/rmuntani/trace2
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ source_code_uri: https://github.com/rmuntani/trace2
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.0.4
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Check the runtime dependencies of your classes
66
+ test_files: []