callgrapher 0.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.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ CallGrapher
2
+ ===========
3
+
4
+ Produce class dependency graphs by tracing method calls while code runs. Outputs
5
+ a .dot file for use with [GraphViz](http://www.graphviz.org/).
6
+
7
+ Requirements
8
+ -----------
9
+ Ruby 1.9 or later & Graphviz on your $PATH
10
+
11
+ Usage
12
+ -----
13
+ Require 'callgrapher' and call trace_class_dependencies, passing in a block
14
+ containing the code you want to graph. A Hash of sets will be returned, ready
15
+ for proccesing with make_class_graph. Calling make_class_graph uses the `dot`
16
+ command to generate a graph & save it to /tmp/graph.png.
17
+
18
+ Known Limitations
19
+ -----------------
20
+ It ignores dependencies created by C function calls as the filename doesn't seem
21
+ to reported correctly in these cases.
22
+
23
+ It does not pick up dependencies created by calling methods defined by the
24
+ attr_reader, attr_writer and attr_accessor methods. (see [this][bug] bug report)
25
+ ). There's probably various other meta-programming techniques that will interact
26
+ poorly with this as well.
27
+
28
+ [bug]: http://bugs.ruby-lang.org/issues/4583
29
+
30
+ License
31
+ -------
32
+ Copyright (c) 2010-2012 Luke Andrew
33
+
34
+ All code is distributed under the terms of the [GPL version 3][gpl]. All other
35
+ copyrightable material is distributed under the terms of the [Creative Commons
36
+ Attribution-ShareAlike 3.0 Unported License][cc]
37
+
38
+ Copies of these licenses are available in the files `LICENSE-GPLv3` and `LICENSE-CC-BY-SA`.
39
+
40
+ [gpl]: http://www.gnu.org/licenses/gpl.html
41
+ [cc]: http://creativecommons.org/licenses/by-sa/3.0/
@@ -0,0 +1,87 @@
1
+ # Copyright (C) 2012 Luke Andrew
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ # This is the main executable for the whysynth-controller project. It also
17
+ # contains most of the code that relies on external libraries.
18
+
19
+ require 'set'
20
+
21
+ module CallGrapher
22
+
23
+ # @param [Array<String>] file_whitelist
24
+ # Only include classes defined in these files.
25
+ # @param [Integer] namespace_depth
26
+ # The number of namespaces to report. For example, a class name of
27
+ # Foo::Bar::Baz, when processed with namespace_depth == 1, would be reported
28
+ # as just Foo. With namespace_depth == 2, as Foo::Bar, and so on. A
29
+ # namespace_depth of 0 indicates that the entire class name should be
30
+ # reported.
31
+ def self.trace_class_dependencies(namespace_depth = 0,
32
+ file_whitelist = nil,
33
+ class_blacklist = nil)
34
+ callstack = []
35
+ classgraph = Hash.new{ |hash, key| hash[key] = Set.new }
36
+
37
+ set_trace_func proc{ |event, file, line, id, binding, klass|
38
+ next if file_whitelist && !file_whitelist.include?(file)
39
+ next if class_blacklist && class_blacklist.include?(klass.to_s)
40
+ case event
41
+ when 'call'
42
+ caller = callstack[-1]
43
+
44
+ klass =
45
+ if namespace_depth > 0
46
+ klass.name.split('::').first(namespace_depth).join('::')
47
+ else
48
+ klass.name
49
+ end
50
+
51
+ classgraph[caller].add klass if caller && caller != klass
52
+ callstack.push klass
53
+ when 'return'
54
+ callstack.pop
55
+ end
56
+ }
57
+
58
+ yield
59
+
60
+ set_trace_func nil
61
+ classgraph
62
+ end
63
+
64
+ # @param [Hash<Class, Set>] call_graph
65
+ # the graph to transform into a Graphviz digraph
66
+ def self.make_graphviz_graph(call_graph)
67
+
68
+ graph = ''
69
+ graph << 'digraph callgraph {'
70
+
71
+ call_graph.each do |klass, dependencies|
72
+ graph << "\"#{klass}\" [shape=box];\n"
73
+ dependencies.each do |dependency|
74
+ graph << "\"#{dependency}\" [shape=box];\n"
75
+ graph << "\"#{klass}\" -> \"#{dependency}\";\n"
76
+ end
77
+ end
78
+
79
+ graph << '}'
80
+ end
81
+
82
+ # @param graph a string containing a dot format digraph
83
+ def self.make_graph(graph, path)
84
+ IO.popen("dot -Tpng -o#{path}", 'w') { |out| out.write graph }
85
+ end
86
+
87
+ end
data/test/test.rb ADDED
@@ -0,0 +1,150 @@
1
+ # Copyright (C) 2012 Luke Andrew
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ # This is the main executable for the whysynth-controller project. It also
17
+ # contains most of the code that relies on external libraries.
18
+
19
+ require 'callgrapher'
20
+ require 'minitest/autorun'
21
+
22
+ class Class1
23
+ def initialize
24
+ end
25
+ def test
26
+ Class2.new.test
27
+ end
28
+ end
29
+
30
+ class Class2
31
+ def test
32
+ M1::M2::Class3.new.test
33
+ Class4.new.test
34
+ end
35
+ end
36
+
37
+ module M1
38
+ module M2
39
+ class Class3
40
+ def test
41
+ M3::Class5.test
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ class Class4
48
+ def test
49
+ M3::Class5.test
50
+ end
51
+ end
52
+
53
+ module M3
54
+ class Class5
55
+
56
+ def self.test
57
+ self.self_call
58
+ Class1.new
59
+ end
60
+
61
+ def self.self_call
62
+ end
63
+
64
+ end
65
+ end
66
+
67
+ class Test < MiniTest::Unit::TestCase
68
+
69
+ ExpectedTestGraph = {
70
+ 'Class1' => Set.new(['Class2']) ,
71
+ 'Class2' => Set.new(['M1::M2::Class3', 'Class4']) ,
72
+ 'M1::M2::Class3' => Set.new(['M3::Class5']) ,
73
+ 'Class4' => Set.new(['M3::Class5']) ,
74
+ 'M3::Class5' => Set.new(['Class1']) }
75
+
76
+ ExpectedTestGraph_NamespaceDepth1 = {
77
+ 'Class1' => Set.new(['Class2']) ,
78
+ 'Class2' => Set.new(['M1', 'Class4']) ,
79
+ 'M1' => Set.new(['M3']) ,
80
+ 'Class4' => Set.new(['M3']) ,
81
+ 'M3' => Set.new(['Class1']) }
82
+
83
+ ExpectedTestGraph_NamespaceDepth2 = {
84
+ 'Class1' => Set.new(['Class2']) ,
85
+ 'Class2' => Set.new(['M1::M2', 'Class4']) ,
86
+ 'M1::M2' => Set.new(['M3::Class5']) ,
87
+ 'Class4' => Set.new(['M3::Class5']) ,
88
+ 'M3::Class5' => Set.new(['Class1']) }
89
+
90
+ ExpectedTestGraph_WithoutClass4 = {
91
+ 'Class1' => Set.new(['Class2']) ,
92
+ 'Class2' => Set.new(['M1::M2::Class3', 'M3::Class5']) ,
93
+ 'M1::M2::Class3' => Set.new(['M3::Class5']) ,
94
+ 'M3::Class5' => Set.new(['Class1']) }
95
+
96
+ ExpectedGraphvizGraph = 'digraph callgraph {"Class1" [shape=box];
97
+ "Class2" [shape=box];
98
+ "Class1" -> "Class2";
99
+ "Class2" [shape=box];
100
+ "M1::M2::Class3" [shape=box];
101
+ "Class2" -> "M1::M2::Class3";
102
+ "Class4" [shape=box];
103
+ "Class2" -> "Class4";
104
+ "M1::M2::Class3" [shape=box];
105
+ "M3::Class5" [shape=box];
106
+ "M1::M2::Class3" -> "M3::Class5";
107
+ "Class4" [shape=box];
108
+ "M3::Class5" [shape=box];
109
+ "Class4" -> "M3::Class5";
110
+ "M3::Class5" [shape=box];
111
+ "Class1" [shape=box];
112
+ "M3::Class5" -> "Class1";
113
+ }'
114
+
115
+ EmptyHash = {}
116
+
117
+ def test_class_dependency_tracing
118
+ assert_equal ExpectedTestGraph, CallGrapher.trace_class_dependencies{ Class1.new.test }
119
+ end
120
+
121
+ def test_graphviz_output
122
+ assert_equal ExpectedGraphvizGraph, CallGrapher.make_graphviz_graph(ExpectedTestGraph)
123
+ end
124
+
125
+ def test_file_whitelist
126
+ assert_equal EmptyHash,
127
+ CallGrapher.trace_class_dependencies(0, []){ Class1.new.test }
128
+
129
+ assert_equal ExpectedTestGraph,
130
+ CallGrapher.trace_class_dependencies(0, [__FILE__]) { Class1.new.test}
131
+ end
132
+
133
+ def test_namespace_depth
134
+ assert_equal ExpectedTestGraph_NamespaceDepth1,
135
+ CallGrapher.trace_class_dependencies(1) { Class1.new.test }
136
+
137
+ assert_equal ExpectedTestGraph_NamespaceDepth2,
138
+ CallGrapher.trace_class_dependencies(2) { Class1.new.test }
139
+ end
140
+
141
+ def test_class_blacklist
142
+ assert_equal ExpectedTestGraph_WithoutClass4, CallGrapher.trace_class_dependencies(0, [__FILE__], ['Class4']) { Class1.new.test }
143
+ end
144
+
145
+ # This test doesn't assert anything, it's just convenient to have the tests
146
+ # call Graphviz for you so you can manually inspect the output.
147
+ def test_make_graph
148
+ CallGrapher.make_graph ExpectedGraphvizGraph, './graph.png'
149
+ end
150
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: callgrapher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Luke Andrew
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-02 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Produce a call graph graph by tracing the method calls of running code.
15
+ The output is a .dot file for use with GraphViz
16
+ email:
17
+ - luke.callgrapher@la.id.au
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - lib/callgrapher.rb
23
+ - test/test.rb
24
+ - LICENSE-CC-BY-SA
25
+ - LICENSE-GPLv3
26
+ - README.md
27
+ homepage:
28
+ licenses: []
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '1.9'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 1.8.23
48
+ signing_key:
49
+ specification_version: 3
50
+ summary: Produce call graphs of your Ruby code
51
+ test_files: []
52
+ has_rdoc: