rubydeps 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +17 -6
- data/bin/rubydeps +15 -6
- data/lib/rubydeps.rb +17 -9
- data/spec/rubydeps_spec.rb +49 -1
- metadata +3 -3
data/README.md
CHANGED
@@ -12,24 +12,30 @@ Rubydeps will run your test suite to record the call graph of your project and u
|
|
12
12
|
First of all, be sure to step into the root directory of your project, rubydeps searches for ./spec or ./test dirs from there.
|
13
13
|
For example, if we want to graph the Rails activemodel dependency graph we'd cd to rails/activemodel and from there we'd write:
|
14
14
|
|
15
|
-
rubydeps testunit
|
15
|
+
rubydeps testunit #to run Test::Unit tests
|
16
16
|
or
|
17
|
-
rubydeps rspec
|
17
|
+
rubydeps rspec #to run RSpec tests
|
18
18
|
or
|
19
|
-
rubydeps rspec2
|
19
|
+
rubydeps rspec2 #to run RSpec 2 tests
|
20
20
|
|
21
|
-
This will output a rubydeps.dot.
|
22
|
-
You can convert the dot file to any image format you like using the dot utility that comes with the graphviz installation e.g.:
|
21
|
+
This will output a rubydeps.dot. You can convert the dot file to any image format you like using the dot utility that comes with the graphviz installation e.g.:
|
23
22
|
|
24
23
|
dot -Tsvg rubydeps.dot > rubydeps.svg
|
25
24
|
|
25
|
+
### Command line options
|
26
|
+
|
27
|
+
The --path-filter option specifies a regexp that matches the path of the files you are interested in analyzing. For example you could have filters like 'project_name/app|project_name/lib' to analyze only code that is located in the 'app' and 'lib' dirs or as an alternative you could just exclude some directory you are not interested using a negative regexp like 'project_name(?!.*test)'
|
28
|
+
|
29
|
+
The --class_name_filter option is similar to the --path_filter options except that the regexp is matched against the class names (i.e. graph node names).
|
26
30
|
|
27
31
|
Library usage
|
28
32
|
-------------
|
29
33
|
|
30
34
|
Just require rubydeps and pass a block to analyze to the dot_for method.
|
31
35
|
|
32
|
-
|
36
|
+
require 'rubydeps'
|
37
|
+
|
38
|
+
Rubydeps.dot_for(:path_filter => path_filter_regexp, :class_name_filter) do
|
33
39
|
//your code goes here
|
34
40
|
end
|
35
41
|
|
@@ -42,6 +48,11 @@ This is the result of running rubydeps on the [Mechanize](http://github.com/tend
|
|
42
48
|
|
43
49
|
Notice that sometimes you may have missing dependencies as we graph the dependencies exercised by your tests so it's a quick bird's eye view to check your project coverage.
|
44
50
|
|
51
|
+
Installation
|
52
|
+
------------
|
53
|
+
|
54
|
+
gem install rubydeps
|
55
|
+
|
45
56
|
Dependencies
|
46
57
|
------------
|
47
58
|
|
data/bin/rubydeps
CHANGED
@@ -6,13 +6,13 @@ module Rubydeps
|
|
6
6
|
class Runner < Thor
|
7
7
|
desc "testunit", "Create the dependency graph after runnning the testunit tests"
|
8
8
|
method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
|
9
|
+
method_option :class_name_filter, :type => :string, :default => '', :required => true
|
9
10
|
def testunit
|
10
11
|
require 'test/unit'
|
11
12
|
|
12
13
|
(Dir["./test/**/*_test.rb"] + Dir["./test/**/test_*.rb"]).each { |f| load f unless f =~ /^-/ }
|
13
14
|
|
14
|
-
|
15
|
-
Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
|
15
|
+
create_dependencies_dot_for(options) do
|
16
16
|
::Test::Unit::AutoRunner.run
|
17
17
|
end
|
18
18
|
end
|
@@ -20,6 +20,7 @@ module Rubydeps
|
|
20
20
|
desc "rspec", "Create the dependency graph after runnning the rspec tests"
|
21
21
|
#TODO: this breaks when using underscores, investigate
|
22
22
|
method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
|
23
|
+
method_option :class_name_filter, :type => :string, :default => '', :required => true
|
23
24
|
def rspec
|
24
25
|
require 'spec'
|
25
26
|
|
@@ -27,25 +28,33 @@ module Rubydeps
|
|
27
28
|
p.parse(Dir["./spec/**/*_spec.rb"])
|
28
29
|
op = p.options
|
29
30
|
|
30
|
-
|
31
|
-
Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
|
31
|
+
create_dependencies_dot_for(options) do
|
32
32
|
::Spec::Runner::CommandLine.run(op)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
36
|
desc "rspec2", "Create the dependency graph after runnning the rspec 2 tests"
|
37
37
|
method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
|
38
|
+
method_option :class_name_filter, :type => :string, :default => '', :required => true
|
38
39
|
def rspec2
|
39
40
|
require 'rspec'
|
40
41
|
|
41
42
|
op = ::RSpec::Core::ConfigurationOptions.new(Dir["./spec/**/*_spec.rb"])
|
42
43
|
op.parse_options
|
43
44
|
|
44
|
-
|
45
|
-
Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
|
45
|
+
create_dependencies_dot_for(options) do
|
46
46
|
::RSpec::Core::CommandLine.new(op).run($stderr, $stdout)
|
47
47
|
end
|
48
48
|
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def create_dependencies_dot_for(options)
|
53
|
+
ARGV.clear
|
54
|
+
Rubydeps.dot_for(:path_filter => Regexp.new(options[:path_filter]), :class_name_filter => Regexp.new(options[:class_name_filter])) do
|
55
|
+
yield
|
56
|
+
end
|
57
|
+
end
|
49
58
|
end
|
50
59
|
end
|
51
60
|
|
data/lib/rubydeps.rb
CHANGED
@@ -4,8 +4,8 @@ require 'rcovrt'
|
|
4
4
|
require 'rcov'
|
5
5
|
|
6
6
|
module Rubydeps
|
7
|
-
def self.dot_for(
|
8
|
-
dependencies_hash = hash_for(
|
7
|
+
def self.dot_for(options = {}, &block_to_analyze)
|
8
|
+
dependencies_hash = hash_for(options, &block_to_analyze)
|
9
9
|
|
10
10
|
if dependencies_hash
|
11
11
|
g = GraphViz::new( "G", :use => 'dot', :mode => 'major', :rankdir => 'LR', :concentrate => 'true', :fontname => 'Arial')
|
@@ -27,14 +27,17 @@ module Rubydeps
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def self.hash_for(
|
30
|
+
def self.hash_for(options = {}, &block_to_analyze)
|
31
31
|
analyzer = Rcov::CallSiteAnalyzer.new
|
32
32
|
analyzer.run_hooked do
|
33
33
|
block_to_analyze.call
|
34
34
|
end
|
35
35
|
|
36
|
+
path_filter = options.fetch(:path_filter, /.*/)
|
37
|
+
class_name_filter = options.fetch(:class_name_filter, /.*/)
|
38
|
+
|
36
39
|
dependency_hash = create_dependency_hash(analyzer, path_filter)
|
37
|
-
clean_hash(dependency_hash)
|
40
|
+
clean_hash(dependency_hash, class_name_filter)
|
38
41
|
end
|
39
42
|
|
40
43
|
private
|
@@ -43,6 +46,7 @@ private
|
|
43
46
|
end
|
44
47
|
|
45
48
|
#we build a hash structured in this way: {"called_class_name1" => ["calling_class_name1", "calling_class_name2"], "called_class_name2" => ...}
|
49
|
+
#TODO: want moar love here
|
46
50
|
def self.create_dependency_hash(analyzer, path_filter)
|
47
51
|
dependency_hash = {}
|
48
52
|
analyzer.analyzed_classes.each do |c|
|
@@ -51,7 +55,7 @@ private
|
|
51
55
|
called_class_method = "#{c}##{m}"
|
52
56
|
def_site = analyzer.defsite(called_class_method)
|
53
57
|
if path_filtered_site?(def_site, path_filter)
|
54
|
-
calling_class_names =
|
58
|
+
calling_class_names = Set.new
|
55
59
|
analyzer.callsites(called_class_method).each do |call_site, _|
|
56
60
|
if path_filtered_site?(call_site, path_filter)
|
57
61
|
calling_class = call_site.calling_class
|
@@ -59,7 +63,7 @@ private
|
|
59
63
|
calling_class_names << calling_class_name
|
60
64
|
end
|
61
65
|
end
|
62
|
-
dependency_hash[called_class_name] ||=
|
66
|
+
dependency_hash[called_class_name] ||= Set.new
|
63
67
|
dependency_hash[called_class_name] += calling_class_names
|
64
68
|
end
|
65
69
|
end
|
@@ -68,11 +72,15 @@ private
|
|
68
72
|
dependency_hash
|
69
73
|
end
|
70
74
|
|
71
|
-
def self.clean_hash(dependency_hash)
|
75
|
+
def self.clean_hash(dependency_hash, class_name_filter)
|
72
76
|
cleaned_hash = {}
|
73
77
|
dependency_hash.each do |called_class_name, calling_class_names|
|
74
|
-
if interesting_class_name(called_class_name) && !dependency_hash[called_class_name].empty?
|
75
|
-
cleaned_hash[called_class_name] = calling_class_names.
|
78
|
+
if interesting_class_name(called_class_name) && !dependency_hash[called_class_name].empty? && called_class_name =~ class_name_filter
|
79
|
+
cleaned_hash[called_class_name] = (calling_class_names - [nil]).select do |c|
|
80
|
+
interesting_class_name(c) &&
|
81
|
+
c != called_class_name &&
|
82
|
+
c =~ class_name_filter
|
83
|
+
end
|
76
84
|
cleaned_hash.delete(called_class_name) if cleaned_hash[called_class_name].empty?
|
77
85
|
end
|
78
86
|
end
|
data/spec/rubydeps_spec.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
#require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
1
|
require 'file_test_helper'
|
3
2
|
|
4
3
|
class Grandparent
|
@@ -86,4 +85,53 @@ describe "Rubydeps" do
|
|
86
85
|
dependencies["Parent"].should == ["Son"]
|
87
86
|
dependencies["Grandparent"].should =~ ["Son", "Parent"]
|
88
87
|
end
|
88
|
+
|
89
|
+
sample_dir_structure = {'path1/class_a.rb' => <<-CLASSA,
|
90
|
+
require 'path1/class_b'
|
91
|
+
require 'path2/class_c'
|
92
|
+
class A
|
93
|
+
def depend_on_b_and_c
|
94
|
+
B.new.b
|
95
|
+
C.new.c
|
96
|
+
end
|
97
|
+
end
|
98
|
+
CLASSA
|
99
|
+
'path1/class_b.rb' => 'class B; def b; end end',
|
100
|
+
'path2/class_c.rb' => 'class C; def c; end end'}
|
101
|
+
|
102
|
+
it "should not filter classes when no filter is specified" do
|
103
|
+
with_files(sample_dir_structure) do
|
104
|
+
load 'path1/class_a.rb'
|
105
|
+
|
106
|
+
dependencies = Rubydeps.hash_for do
|
107
|
+
A.new.depend_on_b_and_c
|
108
|
+
end
|
109
|
+
|
110
|
+
dependencies.should == {"B"=>["A"], "C"=>["A"]}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should filter classes when a path filter is specified" do
|
115
|
+
with_files(sample_dir_structure) do
|
116
|
+
load 'path1/class_a.rb'
|
117
|
+
|
118
|
+
dependencies = Rubydeps.hash_for(:path_filter => /path1/) do
|
119
|
+
A.new.depend_on_b_and_c
|
120
|
+
end
|
121
|
+
|
122
|
+
dependencies.should == {"B"=>["A"]}
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should filter classes when a class name filter is specified" do
|
127
|
+
with_files(sample_dir_structure) do
|
128
|
+
load 'path1/class_a.rb'
|
129
|
+
|
130
|
+
dependencies = Rubydeps.hash_for(:class_name_filter => /C|A/) do
|
131
|
+
A.new.depend_on_b_and_c
|
132
|
+
end
|
133
|
+
|
134
|
+
dependencies.should == {"C"=>["A"]}
|
135
|
+
end
|
136
|
+
end
|
89
137
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubydeps
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Daniel Cadenas
|