rubydeps 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Daniel Cadenas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ rubydeps
2
+ ========
3
+
4
+ A tool to create class dependency graphs from test suites
5
+
6
+ Command line usage
7
+ ------------------
8
+
9
+
10
+ Rubydeps will run your test suite to record the call graph of your project and use it to create a dot graph.
11
+
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
+ 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
+
15
+ rubydeps testunit --path-filter='activemodel/lib' # to run Test::Unit tests
16
+ or
17
+ rubydeps rspec --path-filter='activemodel/lib' # to run RSpec tests
18
+ or
19
+ rubydeps rspec2 --path-filter='activemodel/lib' # to run RSpec 2 tests
20
+
21
+ This will output a rubydeps.dot. 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)'
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.:
23
+
24
+ dot -Tsvg rubydeps.dot > rubydeps.svg
25
+
26
+
27
+ Library usage
28
+ -------------
29
+
30
+ Just require rubydeps and pass a block to analyze to the dot_for method.
31
+
32
+ Rubydeps.dot_for(path_filter_regexp) do
33
+ //your code goes here
34
+ end
35
+
36
+ Sample output
37
+ -------------
38
+
39
+ This is the result of running rubydeps on the [Mechanize](http://github.com/tenderlove/mechanize) tests:
40
+
41
+ ![Mechanize dependencies](https://github.com/dcadenas/rubydeps/raw/master/mechanize-deps.png)
42
+
43
+ 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
+
45
+ Dependencies
46
+ ------------
47
+
48
+ * rcov
49
+ * graphviz
50
+ * ruby-graphviz
51
+
52
+ Note on Patches/Pull Requests
53
+ -----------------------------
54
+
55
+ * Fork the project.
56
+ * Make your feature addition or bug fix.
57
+ * Add tests for it. This is important so I don't break it in a
58
+ future version unintentionally.
59
+ * Commit, do not mess with rakefile, version, or history.
60
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
61
+ * Send me a pull request. Bonus points for topic branches.
62
+
63
+ Copyright
64
+ ---------
65
+
66
+ Copyright (c) 2010 Daniel Cadenas. See LICENSE for details.
67
+
68
+ Development sponsored by [Cubox](http://www.cuboxsa.com)
data/bin/rubydeps ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubydeps'
3
+ require 'thor'
4
+
5
+ module Rubydeps
6
+ class Runner < Thor
7
+ desc "testunit", "Create the dependency graph after runnning the testunit tests"
8
+ method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
9
+ def testunit
10
+ require 'test/unit'
11
+
12
+ (Dir["./test/**/*_test.rb"] + Dir["./test/**/test_*.rb"]).each { |f| load f unless f =~ /^-/ }
13
+
14
+ ARGV.clear
15
+ Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
16
+ ::Test::Unit::AutoRunner.run
17
+ end
18
+ end
19
+
20
+ desc "rspec", "Create the dependency graph after runnning the rspec tests"
21
+ #TODO: this breaks when using underscores, investigate
22
+ method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
23
+ def rspec
24
+ require 'spec'
25
+
26
+ p = ::Spec::Runner::OptionParser.new($stderr, $stdout)
27
+ p.parse(Dir["./spec/**/*_spec.rb"])
28
+ op = p.options
29
+
30
+ ARGV.clear
31
+ Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
32
+ ::Spec::Runner::CommandLine.run(op)
33
+ end
34
+ end
35
+
36
+ desc "rspec2", "Create the dependency graph after runnning the rspec 2 tests"
37
+ method_option :path_filter, :type => :string, :default => `pwd`.chomp, :required => true
38
+ def rspec2
39
+ require 'rspec'
40
+
41
+ op = ::RSpec::Core::ConfigurationOptions.new(Dir["./spec/**/*_spec.rb"])
42
+ op.parse_options
43
+
44
+ ARGV.clear
45
+ Rubydeps.dot_for(Regexp.new(options[:path_filter])) do
46
+ ::RSpec::Core::CommandLine.new(op).run($stderr, $stdout)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ Rubydeps::Runner.start(ARGV)
53
+
data/lib/rubydeps.rb ADDED
@@ -0,0 +1,92 @@
1
+ require 'graphviz'
2
+ require 'set'
3
+ require 'rcovrt'
4
+ require 'rcov'
5
+
6
+ module Rubydeps
7
+ def self.dot_for(path_filter = /.*/, &block_to_analyze)
8
+ dependencies_hash = hash_for(path_filter, &block_to_analyze)
9
+
10
+ if dependencies_hash
11
+ g = GraphViz::new( "G", :use => 'dot', :mode => 'major', :rankdir => 'LR', :concentrate => 'true', :fontname => 'Arial')
12
+ dependencies_hash.each do |k,vs|
13
+ if !k.empty? && !vs.empty?
14
+ n1 = g.add_node(k.to_s)
15
+ if vs.respond_to?(:each)
16
+ vs.each do |v|
17
+ unless v.empty?
18
+ n2 = g.add_node(v.to_s)
19
+ g.add_edge(n2, n1)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ g.output( :dot => "rubydeps.dot" )
27
+ end
28
+ end
29
+
30
+ def self.hash_for(path_filter = /.*/, &block_to_analyze)
31
+ analyzer = Rcov::CallSiteAnalyzer.new
32
+ analyzer.run_hooked do
33
+ block_to_analyze.call
34
+ end
35
+
36
+ dependency_hash = create_dependency_hash(analyzer, path_filter)
37
+ clean_hash(dependency_hash)
38
+ end
39
+
40
+ private
41
+ def self.path_filtered_site?(code_site, path_filter)
42
+ code_site && path_filter =~ File.expand_path(code_site.file)
43
+ end
44
+
45
+ #we build a hash structured in this way: {"called_class_name1" => ["calling_class_name1", "calling_class_name2"], "called_class_name2" => ...}
46
+ def self.create_dependency_hash(analyzer, path_filter)
47
+ dependency_hash = {}
48
+ analyzer.analyzed_classes.each do |c|
49
+ called_class_name = normalize_class_name(c)
50
+ analyzer.methods_for_class(c).each do |m|
51
+ called_class_method = "#{c}##{m}"
52
+ def_site = analyzer.defsite(called_class_method)
53
+ if path_filtered_site?(def_site, path_filter)
54
+ calling_class_names = []
55
+ analyzer.callsites(called_class_method).each do |call_site, _|
56
+ if path_filtered_site?(call_site, path_filter)
57
+ calling_class = call_site.calling_class
58
+ calling_class_name = normalize_class_name(calling_class.to_s)
59
+ calling_class_names << calling_class_name
60
+ end
61
+ end
62
+ dependency_hash[called_class_name] ||= []
63
+ dependency_hash[called_class_name] += calling_class_names
64
+ end
65
+ end
66
+ end
67
+
68
+ dependency_hash
69
+ end
70
+
71
+ def self.clean_hash(dependency_hash)
72
+ cleaned_hash = {}
73
+ 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.compact.uniq.select{|c| interesting_class_name(c) && c != called_class_name }
76
+ cleaned_hash.delete(called_class_name) if cleaned_hash[called_class_name].empty?
77
+ end
78
+ end
79
+
80
+ cleaned_hash
81
+ end
82
+
83
+ def self.interesting_class_name(class_name)
84
+ !class_name.empty? && class_name != "Rcov::CallSiteAnalyzer" && class_name != "Rcov::DifferentialAnalyzer"
85
+ end
86
+
87
+ def self.normalize_class_name(klass)
88
+ good_class_name = klass.gsub(/#<Class:(.+)>/, '\1')
89
+ good_class_name.gsub!(/\([^\)]*\)/, "")
90
+ good_class_name.gsub(/0x[\da-fA-F]+/, '(hex number)')
91
+ end
92
+ end
@@ -0,0 +1,89 @@
1
+ #require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'file_test_helper'
3
+
4
+ class Grandparent
5
+ def self.class_method
6
+ end
7
+
8
+ def instance_method
9
+ end
10
+ end
11
+
12
+ class Parent
13
+ def self.class_method
14
+ Grandparent.class_method
15
+ end
16
+
17
+ def instance_method
18
+ end
19
+ end
20
+
21
+ class Son
22
+ def self.class_method
23
+ parent = Parent.new
24
+ parent.instance_method
25
+ parent.instance_method
26
+ class_method2
27
+ class_method2
28
+ end
29
+
30
+ def self.class_method2
31
+ end
32
+
33
+ def instance_method
34
+ Parent.class_method
35
+ Grandparent.class_method
36
+ end
37
+ end
38
+
39
+ describe "Rubydeps" do
40
+ include FileTestHelper
41
+ it "should show the class level dependencies" do
42
+ dependencies = Rubydeps.hash_for do
43
+ class IHaveAClassLevelDependency
44
+ Son.class_method
45
+ end
46
+ end
47
+
48
+ dependencies.should == {"Parent"=>["Son"]}
49
+ end
50
+
51
+ it "should create a dot file" do
52
+ with_files do
53
+ dependencies = Rubydeps.dot_for do
54
+ class IHaveAClassLevelDependency
55
+ Son.class_method
56
+ end
57
+ end
58
+
59
+ File.read("rubydeps.dot").should match("digraph G")
60
+ end
61
+ end
62
+
63
+ it "should be idempotent" do
64
+ Rubydeps.hash_for do
65
+ class IHaveAClassLevelDependency
66
+ Son.class_method
67
+ end
68
+ end
69
+
70
+ dependencies = Rubydeps.hash_for do
71
+ class IHaveAClassLevelDependency
72
+ Son.class_method
73
+ end
74
+ end
75
+
76
+ dependencies.should == {"Parent"=>["Son"]}
77
+ end
78
+
79
+ it "should show the dependencies between the classes inside the block" do
80
+ dependencies = Rubydeps.hash_for do
81
+ Son.class_method
82
+ Son.new.instance_method
83
+ end
84
+
85
+ dependencies.keys.should =~ ["Parent", "Grandparent"]
86
+ dependencies["Parent"].should == ["Son"]
87
+ dependencies["Grandparent"].should =~ ["Son", "Parent"]
88
+ end
89
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubydeps'
2
+ require 'rspec'
3
+ require 'rspec/autorun'
4
+
5
+ Rspec.configure do |config|
6
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubydeps
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Daniel Cadenas
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-28 00:00:00 -03:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 25
30
+ segments:
31
+ - 2
32
+ - 5
33
+ - 1
34
+ version: 2.5.1
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: file_test_helper
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 23
46
+ segments:
47
+ - 1
48
+ - 0
49
+ - 0
50
+ version: 1.0.0
51
+ type: :development
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: ruby-graphviz
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 25
62
+ segments:
63
+ - 0
64
+ - 9
65
+ - 17
66
+ version: 0.9.17
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: rcov
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 43
78
+ segments:
79
+ - 0
80
+ - 9
81
+ - 8
82
+ version: 0.9.8
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: thor
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ hash: 35
94
+ segments:
95
+ - 0
96
+ - 14
97
+ - 2
98
+ version: 0.14.2
99
+ type: :runtime
100
+ version_requirements: *id005
101
+ description: Graphs ruby dependencies
102
+ email: dcadenas@gmail.com
103
+ executables:
104
+ - rubydeps
105
+ extensions: []
106
+
107
+ extra_rdoc_files:
108
+ - LICENSE
109
+ files:
110
+ - .document
111
+ - .gitignore
112
+ - LICENSE
113
+ - README.md
114
+ - bin/rubydeps
115
+ - lib/rubydeps.rb
116
+ - spec/rubydeps_spec.rb
117
+ - spec/spec_helper.rb
118
+ has_rdoc: true
119
+ homepage: http://github.com/dcadenas/rubydeps
120
+ licenses: []
121
+
122
+ post_install_message:
123
+ rdoc_options:
124
+ - --charset=UTF-8
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ hash: 3
133
+ segments:
134
+ - 0
135
+ version: "0"
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ requirements: []
146
+
147
+ rubyforge_project:
148
+ rubygems_version: 1.6.2
149
+ signing_key:
150
+ specification_version: 3
151
+ summary: Graphs ruby depencencies
152
+ test_files:
153
+ - spec/rubydeps_spec.rb
154
+ - spec/spec_helper.rb