ruby-mass 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ .rvmrc
4
+ .DS_Store
5
+ Gemfile.lock
6
+ doc
7
+ pkg
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = RubyMass CHANGELOG
2
+
3
+ == Version 0.1.0 (June 9, 2012)
4
+
5
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :gem_default do
6
+ gem "ruby-mass", :path => "."
7
+ end
8
+
9
+ group :gem_development do
10
+ gem "horo"
11
+ gem "pry"
12
+ end
13
+
14
+ group :gem_test do
15
+ gem "minitest"
16
+ gem "mocha"
17
+ gem "pry"
18
+ end
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Paul Engel
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.rdoc ADDED
@@ -0,0 +1,177 @@
1
+ == RubyMass
2
+
3
+ Introspect the Ruby Heap by indexing, counting, locating references to and detaching (in order to release) objects - optionally narrowing by namespace.
4
+
5
+ === Introduction
6
+
7
+ Memory management is an important factor within every application. It can slow down the performance of your application and even of your system. Unlike other programming languages, such as C or Pascal, Ruby does not let you handle the memory allocation directly.
8
+
9
+ The garbage collector (<tt>GC</tt>) controls releasing allocated memory. In order to let the GC free an object, you will have to ensure that there are no references to that object or else your memory usage will grow. Finding out why memory does not get released (memory leaks) is like finding a needle in a haystack.
10
+
11
+ RubyMass can help you tackle memory leaks as you can list (and count) which Ruby objects (<tt>Ruby mass :D</tt>) are in memory. Also, you can locate references to a certain object and remove them if wanted. Try out RubyMass and feel free to send in pull requests and/or suggestions.
12
+
13
+ <b>Note: RubyMass is successfully tested using Ruby 1.8.7, Ruby 1.9.2 and Ruby 1.9.3</b>
14
+
15
+ === Installation
16
+
17
+ ==== Using Bundler
18
+
19
+ Add RubyMass in <tt>Gemfile</tt> as a gem dependency:
20
+
21
+ gem "ruby-mass"
22
+
23
+ Run the following in your console to install with Bundler:
24
+
25
+ bundle install
26
+
27
+ === Usage
28
+
29
+ Using RubyMass is pretty straightforward. All examples are based using the following code:
30
+
31
+ class Foo
32
+ class Bar; end
33
+ end
34
+
35
+ class Thing; end
36
+ class OneMoreThing; end
37
+
38
+ ==== Getting an object by object_id
39
+
40
+ This is a shortcut to the <tt>ObjectSpace._id2ref</tt> method:
41
+
42
+ $ f = Foo.new
43
+ $ o = Mass[f.object_id]
44
+ $ f.object_id == o.object_id #=> true
45
+
46
+ ==== Indexing objects
47
+
48
+ Just call <tt>Mass.index</tt> to get a Hash containing class names with object_ids of its instances. You can narrow the result by specifying a namespace.
49
+
50
+ $ Mass.index Foo #=> {}
51
+
52
+ $ f = Foo.new
53
+ $ b1 = Foo::Bar.new
54
+ $ b2 = Foo::Bar.new
55
+ $ t = Thing.new
56
+
57
+ $ f.object_id #=> 2154166500
58
+ $ b1.object_id #=> 2154037400
59
+ $ b2.object_id #=> 2154126780
60
+ $ t.object_id #=> 2154372180
61
+
62
+ $ Mass.index Foo #=> {"Foo"=>[2154166500], "Foo::Bar"=>[2154037400, 2154126780]}
63
+ $ Mass.index Foo::Bar #=> {"Foo::Bar"=>[2154037400, 2154126780]}
64
+ $ Mass.index Thing #=> {"Thing"=>[2154372180]}
65
+ $ Mass.index #=> {"String"=>[2151797000, 2151797080, 2151797120, 2151797140, ... a lot of more classes and object_ids}
66
+ $ Mass.index OneMoreThing #=> {}
67
+
68
+ ==== Counting objects
69
+
70
+ The method <tt>Mass.count</tt> returns a similar Hash as <tt>Mass.index</tt>, but instead of arrays of object_ids it contains the number of objects.
71
+ Continueing with the previous example, you will get the following results:
72
+
73
+ $ Mass.count Foo #=> {"Foo"=>1, "Foo::Bar"=>2}
74
+ $ Mass.count Foo::Bar #=> {"Foo::Bar"=>2}
75
+ $ Mass.count Thing #=> {"Thing"=>1}
76
+ $ Mass.count #=> {"String"=>37589, "Array"=>11883, ... a lot of more classes and amounts}
77
+ $ Mass.count OneMoreThing #=> {}
78
+
79
+ ==== Pretty print the amount of objects in memory
80
+
81
+ You can also pretty print the result of the <tt>Object.count</tt> method:
82
+
83
+
84
+ $ Mass.print Foo
85
+ +==================================================+
86
+ Objects within Foo namespace
87
+ +==================================================+
88
+ Foo::Bar: 2
89
+ Foo: 1
90
+ +==================================================+
91
+
92
+ $ Mass.print
93
+ +==================================================+
94
+ Objects within environment
95
+ +==================================================+
96
+ String: 37811
97
+ Array: 11899
98
+ RubyVM::InstructionSequence: 4225
99
+ Gem::Version: 2765
100
+ ... a lot of more classes and amounts
101
+ +==================================================+
102
+
103
+ $ Mass.print OneMoreThing
104
+ +==================================================+
105
+ Objects within OneMoreThing namespace
106
+ +==================================================+
107
+ - no objects instantiated -
108
+ +==================================================+
109
+
110
+ ==== Locating object references
111
+
112
+ Along with indexing (and counting) objects, locating object references is very important when solving memory leakage. An example:
113
+
114
+ class Foo
115
+ attr_accessor :foo
116
+ end
117
+
118
+ class Bar
119
+ attr_accessor :fool
120
+ end
121
+
122
+ $ foo1 = Foo.new
123
+ $ Mass.references(foo1) #=> {}
124
+
125
+ $ foo2 = Foo.new
126
+ $ foo2.object_id #=> 2155577620
127
+ $ foo2.foo = foo1
128
+
129
+ $ bar = Bar.new
130
+ $ bar.object_id #=> 2155383980
131
+ $ bar.fool = foo1
132
+
133
+ $ Mass.references(foo1) #=> {"Foo#2155577620" => ["@foo"], "Bar#2155383980" => ["@fool"]}
134
+ $ Mass.references(foo1, Foo) #=> {"Foo#2155577620" => ["@foo"]}
135
+ $ Mass.references(foo1, Bar) #=> {"Bar#2155383980" => ["@fool"]}
136
+ $ Mass.references(foo1, Hash) #=> {}
137
+
138
+ ==== Detaching objects (free allocated memory)
139
+
140
+ After locating references to a certain object, you must remove them in order to let the garbage collector free the allocated memory of that object.
141
+ Just call the <tt>Mass.detach</tt> method and it will remove the object references and invoke <tt>GC.start</tt> instructing the garbage collector to do its job.
142
+ Continueing with the previous example, you will get the following results:
143
+
144
+ $ foo1.object_id #=> 2164554700
145
+ $ Mass.index(Foo) #=> {"Foo"=>[2155577620, 2164554700]}
146
+
147
+ # NOTE: Set foo1 to nil (within the block which only gets yielded after successfully detaching references) as it also is a pointer to the allocated memory
148
+ $ Mass.detach(foo1){foo1 = nil} #=> true
149
+
150
+ # NOTE: This ensures that Mass invokes GC.start but only after verifying that foo1 is nil pointer
151
+ $ Mass.gc!(foo1)
152
+
153
+ $ Mass.index(Foo) #=> {"Foo"=>[2155577620]}
154
+
155
+ <b>Note</b>: Use <tt>Mass.detach</tt> at <b>own risk</b>. Be sure you know what you are doing!
156
+
157
+ === Last remarks
158
+
159
+ Please check out {test/unit/test_mass.rb}[https://github.com/archan937/ruby-mass/blob/master/test/unit/test_mass.rb]. You can run the unit tests with <tt>rake</tt> within the terminal.
160
+ Also, the RubyMass repo is provided with <tt>script/console</tt> which you can use for testing purposes.
161
+
162
+ === Contact me
163
+
164
+ For support, remarks and requests please mail me at {paul.engel@holder.nl}[mailto:paul.engel@holder.nl].
165
+
166
+ === License
167
+
168
+ Copyright (c) 2012 Paul Engel, released under the MIT license
169
+
170
+ http://holder.nl – http://codehero.es – http://gettopup.com – http://github.com/archan937 – http://twitter.com/archan937 –
171
+ {paul.engel@holder.nl}[mailto:paul.engel@holder.nl]
172
+
173
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
174
+
175
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
176
+
177
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ task :default => :test
6
+
7
+ Rake::TestTask.new do |test|
8
+ test.pattern = "test/**/test_*.rb"
9
+ end
10
+
11
+ desc "Generate documentation for RubyMass"
12
+ task :rdoc do
13
+ system "rdoc -f horo -t 'RubyMass documentation' -a README.rdoc lib/*"
14
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/core_ext.rb ADDED
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path|
2
+ require path
3
+ end
@@ -0,0 +1,40 @@
1
+ class Object
2
+
3
+ # A convenience method for detaching all class instances with detach
4
+ #
5
+ def self.detach_all
6
+ Mass.index(self.class)[self.class.name].each{|object_id| Mass[object_id].detach}
7
+ end
8
+
9
+ # A convenience method for detaching all class instances with detach!
10
+ #
11
+ def self.detach_all!
12
+ Mass.index(self.class)[self.class.name].each{|object_id| Mass[object_id].detach!}
13
+ end
14
+
15
+ # Mass uses this method to derive object references. Override this method for increasing performance.
16
+ # In order to override this method, you would probably want to use <tt>Mass.references</tt> of a sample instance.
17
+ #
18
+ def _reference_instances(*mods)
19
+ Mass.send(:instances_within, *mods)
20
+ end
21
+
22
+ # A convenience method for Mass.references
23
+ #
24
+ def references(*mods)
25
+ Mass.references(self, *mods)
26
+ end
27
+
28
+ # A convenience method for Mass.detach
29
+ #
30
+ def detach(*mods, &block)
31
+ Mass.detach(self, *mods, &block)
32
+ end
33
+
34
+ # A convenience method for Mass.detach!
35
+ #
36
+ def detach!(*mods, &block)
37
+ Mass.detach!(self, *mods, &block)
38
+ end
39
+
40
+ end
data/lib/mass.rb ADDED
@@ -0,0 +1,234 @@
1
+ # == Mass
2
+ #
3
+ # This module offers the following either within the whole Ruby Heap or narrowed by namespace:
4
+ #
5
+ # - indexing objects grouped by class
6
+ # - counting objects grouped by class
7
+ # - printing objects grouped by class
8
+ # - locating references to a specified object
9
+ # - detaching references to a specified object (in order to be able to release the object memory using the GC)
10
+ #
11
+ module Mass
12
+ extend self
13
+
14
+ # Returns the object corresponding to the passed object_id.
15
+ #
16
+ # ==== Example
17
+ #
18
+ # log_line = LogLine.new
19
+ # object = Mass[log_line.object_id]
20
+ # log_line.object_id == object.object_id #=> true
21
+ #
22
+ def [](object_id)
23
+ ObjectSpace._id2ref object_id
24
+ end
25
+
26
+ # A helper method after having called <tt>Mass.detach</tt>. It instructs the garbage collector to start when the optional passed variable is nil.
27
+ #
28
+ def gc!(variable = nil)
29
+ GC.start if variable.nil?
30
+ end
31
+
32
+ # Returns a hash containing classes with the object_ids of its instances currently in the Ruby Heap. You can narrow the result by namespace.
33
+ #
34
+ def index(*mods)
35
+ instances_within(*mods).inject({}) do |hash, object|
36
+ ((hash[object.class.name] ||= []) << object.object_id).sort!
37
+ hash
38
+ end
39
+ end
40
+
41
+ # Returns a hash containing classes with the amount of its instances currently in the Ruby Heap. You can narrow the result by namespace.
42
+ #
43
+ def count(*mods)
44
+ index(*mods).inject({}) do |hash, (key, value)|
45
+ hash[key] = value.size
46
+ hash
47
+ end
48
+ end
49
+
50
+ # Prints all object instances within either the whole environment or narrowed by namespace group by class.
51
+ #
52
+ def print(*mods)
53
+ count(*mods).tap do |stats|
54
+ puts "\n"
55
+ puts "=" * 50
56
+ puts " Objects within #{mods ? "#{mods.collect(&:name).sort} namespace" : "environment"}"
57
+ puts "=" * 50
58
+ stats.keys.sort{|a, b| [stats[b], a] <=> [stats[a], b]}.each do |key|
59
+ puts " #{key}: #{stats[key]}"
60
+ end
61
+ puts " - no objects instantiated -" if stats.empty?
62
+ puts "=" * 50
63
+ puts "\n"
64
+ end
65
+ nil
66
+ end
67
+
68
+ # Returns all references to the passed object. You can narrow the namespace of the objects referencing to the object.
69
+ #
70
+ # ==== Example
71
+ #
72
+ # class A
73
+ # attr_accessor :a
74
+ # end
75
+ #
76
+ # class B
77
+ # attr_accessor :a
78
+ # end
79
+ #
80
+ # a1 = A.new
81
+ # Mass.references(a1) #=> {}
82
+ #
83
+ # a2 = A.new
84
+ # a2.object_id #=> 2152675940
85
+ # a2.a = a1
86
+ #
87
+ # b = B.new
88
+ # b.object_id #=> 2152681800
89
+ # b.a = a1
90
+ #
91
+ # Mass.references(a1) #=> {"A#2152675940" => ["@a"], "B#2152681800" => ["@a"]}
92
+ # Mass.references(a1, A) #=> {"A#2152675940" => ["@a"]}
93
+ # Mass.references(a1, B) #=> {"B#2152681800" => ["@a"]}
94
+ # Mass.references(a1, Hash) #=> {}
95
+ #
96
+ def references(object, *mods)
97
+ return {} if object.nil?
98
+ object._reference_instances(*mods).inject({}) do |hash, instance|
99
+ unless (refs = extract_references(instance, object)).empty?
100
+ hash["#{instance.class.name}##{instance.object_id}"] = refs.collect(&:to_s).sort{|a, b| a <=> b}
101
+ end
102
+ hash
103
+ end
104
+ end
105
+
106
+ # Removes all references to the passed object and yielding block when given and references within entire environment removed successfully. Use at own risk.
107
+ #
108
+ def detach(object, *mods, &block)
109
+ _detach(object, object._reference_instances(*mods), true, &block)
110
+ end
111
+
112
+ # Removes all references to the passed object and yielding block when given and references within passed namespaces removed successfully. Use at own risk.
113
+ #
114
+ def detach!(object, *mods, &block)
115
+ _detach(object, object._reference_instances(*mods), false, &block)
116
+ end
117
+
118
+ private
119
+
120
+ # Removes all references to the passed object, yielding block when given and removed references successfully.
121
+ def _detach(object, instances, check_environment, &block)
122
+ return false if object.nil?
123
+ instances = instances.inject([]) do |array, instance|
124
+ references = extract_references(instance, object)
125
+ unless references.empty? || array.any?{|x| x[0].object_id == instance.object_id}
126
+ array << [instance, references]
127
+ end
128
+ array
129
+ end
130
+ removed = instances.all? do |(instance, references)|
131
+ references.all? do |name|
132
+ remove_references(instance, object, name)
133
+ end
134
+ end
135
+ (removed && (!check_environment || references(object).empty?)).tap do |detached|
136
+ yield if detached && block_given?
137
+ end
138
+ end
139
+
140
+ # Returns all classes within a certain namespace.
141
+ #
142
+ def classes_within(mods, initial_array = nil)
143
+ (initial_array || mods.select{|mod| mod.is_a? Class}).tap do |array|
144
+ mods.each do |mod|
145
+ mod.constants.each do |c|
146
+ const = mod.const_get c
147
+ if !array.include?(const) && (const.is_a?(Class) || const.is_a?(Module)) && const.name.match(/^#{mod.name}/)
148
+ if const.is_a?(Class)
149
+ array << const
150
+ end
151
+ if const.is_a?(Class) || const.is_a?(Module)
152
+ classes_within([const], array)
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ # Return all instances. You can narrow the results by passing a namespace.
161
+ #
162
+ def instances_within(*mods)
163
+ GC.start
164
+ [].tap do |array|
165
+ if mods.empty?
166
+ ObjectSpace.each_object do |object|
167
+ array << object
168
+ end
169
+ else
170
+ classes_within(mods).each do |klass|
171
+ ObjectSpace.each_object(klass) do |object|
172
+ array << object
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ # Returns whether the variable equals with or references to (either nested or not) the passed object.
180
+ #
181
+ def matches?(variable, object)
182
+ if variable.object_id == object.object_id
183
+ true
184
+ elsif variable.is_a?(Array) || variable.is_a?(Hash)
185
+ variable.any?{|x| matches? x, object}
186
+ else
187
+ false
188
+ end
189
+ end
190
+
191
+ # Extracts and returns all references between the passed object and instance.
192
+ #
193
+ def extract_references(instance, object)
194
+ instance.instance_variables.select do |name|
195
+ matches? instance.instance_variable_get(name), object
196
+ end
197
+ end
198
+
199
+ # Removes the references between the passed object and instance.
200
+ #
201
+ def remove_references(instance_or_variable, object, name = nil)
202
+ detached = true
203
+ variable = name ? instance_or_variable.instance_variable_get(name) : instance_or_variable
204
+
205
+ if name && variable.object_id == object.object_id
206
+ instance_or_variable.send :remove_instance_variable, name
207
+ elsif variable.is_a?(Array)
208
+ detached = false
209
+ variable.each do |value|
210
+ if value.object_id == object.object_id
211
+ detached = true
212
+ variable.delete value
213
+ elsif value.is_a?(Array) || value.is_a?(Hash)
214
+ detached ||= remove_references(value, object)
215
+ end
216
+ end
217
+ elsif variable.is_a?(Hash)
218
+ detached = false
219
+ variable.each do |key, value|
220
+ if value.object_id == object.object_id
221
+ detached = true
222
+ variable.delete key
223
+ elsif value.is_a?(Array) || value.is_a?(Hash)
224
+ detached ||= remove_references(value, object)
225
+ end
226
+ end
227
+ else
228
+ detached = false
229
+ end
230
+
231
+ detached
232
+ end
233
+
234
+ end
data/lib/ruby-mass.rb ADDED
@@ -0,0 +1,3 @@
1
+ require "core_ext"
2
+ require "mass"
3
+ require "ruby-mass/version"
@@ -0,0 +1,7 @@
1
+ module RubyMass #:nodoc:
2
+ MAJOR = 0
3
+ MINOR = 1
4
+ TINY = 0
5
+
6
+ VERSION = [MAJOR, MINOR, TINY].join(".")
7
+ end
data/ruby-mass.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Paul Engel"]
5
+ gem.email = ["paul.engel@holder.nl"]
6
+ gem.description = %q{Introspect the Ruby Heap by indexing, counting, locating references to and detaching (in order to release) objects - optionally narrowing by namespace}
7
+ gem.summary = %q{Introspect the Ruby Heap by indexing, counting, locating references to and detaching (in order to release) objects - optionally narrowing by namespace}
8
+ gem.homepage = "https://github.com/archan937/ruby-objects"
9
+
10
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
11
+ gem.files = `git ls-files`.split("\n")
12
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
+ gem.name = "ruby-mass"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = "0.1.0"
16
+ end
data/script/console ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "bundler"
4
+
5
+ Bundler.require :gem_default, :gem_development
6
+
7
+ class Foo
8
+ attr_accessor :foo
9
+ class Bar
10
+ attr_accessor :fool
11
+ end
12
+ end
13
+ class Thing
14
+ attr_accessor :food
15
+ end
16
+ class OneMoreThing
17
+ attr_accessor :thing
18
+ end
19
+
20
+ <<-RUBY
21
+ f1 = Foo.new; nil
22
+ f2 = Foo.new
23
+ f2.foo = f1
24
+ b = Foo::Bar.new
25
+ b.fool = f1
26
+ t = Thing.new
27
+ t.food = f1
28
+ object_id = f1.object_id
29
+
30
+ Mass.detach(f1){f1 = nil}
31
+ Mass.gc!(f1)
32
+
33
+ Mass[object_id] rescue nil
34
+ RUBY
35
+
36
+ puts "Loading development environment (RubyMass #{RubyMass::VERSION})"
37
+ puts "[0] pry(main)> defined classes Foo, Foo::Bar, Thing and OneMoreThing"
38
+
39
+ Pry.start
@@ -0,0 +1,7 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+
4
+ Bundler.require :gem_default, :gem_test
5
+
6
+ require "minitest/unit"
7
+ require "minitest/autorun"
@@ -0,0 +1,221 @@
1
+ require File.expand_path("../../test_helper", __FILE__)
2
+
3
+ module Unit
4
+ class TestMass < MiniTest::Unit::TestCase
5
+
6
+ describe Mass do
7
+ before do
8
+ class Foo
9
+ attr_accessor :foo
10
+ class Bar
11
+ attr_accessor :fool
12
+ end
13
+ end
14
+ class Thing
15
+ attr_accessor :food
16
+ end
17
+ class OneMoreThing
18
+ attr_accessor :thing
19
+ end
20
+ end
21
+
22
+ after do
23
+ GC.start
24
+ end
25
+
26
+ it "should be able to return the object corresponding to the passed object_id" do
27
+ f = Foo.new
28
+ o = Mass[f.object_id]
29
+
30
+ assert_equal true, f.object_id == o.object_id
31
+
32
+ f = nil
33
+ o = nil
34
+ end
35
+
36
+ it "should be able to index objects" do
37
+ assert_equal({}, Mass.index(Foo))
38
+
39
+ f = Foo.new
40
+ b1 = Foo::Bar.new
41
+ b2 = Foo::Bar.new
42
+ t = Thing.new
43
+
44
+ assert_equal({"Unit::TestMass::Foo" => [f.object_id], "Unit::TestMass::Foo::Bar" => [b1.object_id, b2.object_id].sort}, Mass.index(Foo))
45
+ assert_equal({"Unit::TestMass::Foo::Bar" => [b1.object_id, b2.object_id].sort}, Mass.index(Foo::Bar))
46
+ assert_equal({"Unit::TestMass::Thing" => [t.object_id]}, Mass.index(Thing))
47
+ assert_equal({"Unit::TestMass::Foo::Bar" => [b1.object_id, b2.object_id].sort, "Unit::TestMass::Thing" => [t.object_id]}, Mass.index(Foo::Bar, Thing))
48
+ assert_equal({}, Mass.index(OneMoreThing))
49
+
50
+ index = Mass.index
51
+ assert_equal true, index.keys.include?("String")
52
+ assert_equal true, index.keys.include?("Array")
53
+ assert_equal true, index.keys.include?("Hash")
54
+ assert_equal true, index["String"].size > 1000
55
+
56
+ f = nil
57
+ b1 = nil
58
+ b2 = nil
59
+ t = nil
60
+ end
61
+
62
+ it "should be able to count objects" do
63
+ assert_equal({}, Mass.count(Foo))
64
+
65
+ f = Foo.new
66
+ b1 = Foo::Bar.new
67
+ b2 = Foo::Bar.new
68
+ t = Thing.new
69
+
70
+ assert_equal({"Unit::TestMass::Foo" => 1, "Unit::TestMass::Foo::Bar" => 2}, Mass.count(Foo))
71
+ assert_equal({"Unit::TestMass::Foo::Bar" => 2}, Mass.count(Foo::Bar))
72
+ assert_equal({"Unit::TestMass::Thing" => 1}, Mass.count(Thing))
73
+ assert_equal({"Unit::TestMass::Foo::Bar" => 2, "Unit::TestMass::Thing" => 1}, Mass.count(Foo::Bar, Thing))
74
+ assert_equal({}, Mass.count(OneMoreThing))
75
+
76
+ count = Mass.count
77
+ assert_equal true, count.keys.include?("String")
78
+ assert_equal true, count.keys.include?("Array")
79
+ assert_equal true, count.keys.include?("Hash")
80
+ assert_equal true, count["String"] > 1000
81
+
82
+ f = nil
83
+ b1 = nil
84
+ b2 = nil
85
+ t = nil
86
+ end
87
+
88
+ it "should tackle nil objects" do
89
+ assert_equal({}, Mass.references(nil))
90
+ assert_equal(false, Mass.detach(nil))
91
+ assert_equal(false, Mass.detach!(nil))
92
+ end
93
+
94
+ # NOTE: The first assertion fails sometimes (maybe multiple test runs related?). If so, retry running the tests.
95
+ it "should be able to locate object references" do
96
+ f1 = Foo.new
97
+ assert_equal({}, Mass.references(f1))
98
+
99
+ f2 = Foo.new
100
+ f2.foo = f1
101
+
102
+ assert_equal({"Unit::TestMass::Foo##{f2.object_id}" => ["@foo"]}, Mass.references(f1))
103
+ assert_equal({"Unit::TestMass::Foo##{f2.object_id}" => ["@foo"]}, Mass.references(f1, Foo))
104
+ assert_equal({}, Mass.references(f1, Foo::Bar))
105
+ assert_equal({}, Mass.references(f1, Foo::Bar, OneMoreThing))
106
+
107
+ b = Foo::Bar.new
108
+ b.fool = f1
109
+
110
+ t = Thing.new
111
+ t.food = f1
112
+
113
+ assert_equal({
114
+ "Unit::TestMass::Foo##{f2.object_id}" => ["@foo"],
115
+ "Unit::TestMass::Foo::Bar##{b.object_id}" => ["@fool"],
116
+ "Unit::TestMass::Thing##{t.object_id}" => ["@food"]
117
+ }, Mass.references(f1))
118
+
119
+ assert_equal({
120
+ "Unit::TestMass::Foo##{f2.object_id}" => ["@foo"],
121
+ "Unit::TestMass::Foo::Bar##{b.object_id}" => ["@fool"]
122
+ }, Mass.references(f1, Foo))
123
+
124
+ assert_equal({
125
+ "Unit::TestMass::Foo##{f2.object_id}" => ["@foo"],
126
+ "Unit::TestMass::Foo::Bar##{b.object_id}" => ["@fool"]
127
+ }, f1.references(Foo, Foo::Bar))
128
+
129
+ assert_equal({
130
+ "Unit::TestMass::Foo::Bar##{b.object_id}" => ["@fool"]
131
+ }, Mass.references(f1, Foo::Bar))
132
+
133
+ assert_equal({
134
+ "Unit::TestMass::Thing##{t.object_id}" => ["@food"]
135
+ }, Mass.references(f1, Thing))
136
+
137
+ assert_equal({}, Mass.references(f1, Hash, OneMoreThing))
138
+
139
+ f1 = nil
140
+ f2 = nil
141
+ b = nil
142
+ t = nil
143
+ end
144
+
145
+ describe "using simple objects" do
146
+ # NOTE: I don't know why the last assertion fails (maybe test environment related?) as the same steps to succeed within script/console (see contents of the file)
147
+ it "should be able to detach objects" do
148
+ f1 = Foo.new
149
+ object_id = f1.object_id
150
+
151
+ assert_equal({}, Mass.references(f1))
152
+
153
+ f2 = Foo.new
154
+ f2.foo = f1
155
+ b = Foo::Bar.new
156
+ b.fool = f1
157
+ t = Thing.new
158
+ t.food = f1
159
+
160
+ assert_equal({
161
+ "Unit::TestMass::Foo" => [object_id, f2.object_id].sort,
162
+ "Unit::TestMass::Foo::Bar" => [b.object_id]
163
+ }, Mass.index(Foo))
164
+ assert_equal({
165
+ "Unit::TestMass::Foo##{f2.object_id}" => ["@foo"],
166
+ "Unit::TestMass::Foo::Bar##{b.object_id}" => ["@fool"],
167
+ "Unit::TestMass::Thing##{t.object_id}" => ["@food"]
168
+ }, Mass.references(f1))
169
+
170
+ assert_equal(false, Mass.detach(f1, OneMoreThing){f1 = nil})
171
+
172
+ assert_equal({
173
+ "Unit::TestMass::Foo" => [object_id, f2.object_id].sort,
174
+ "Unit::TestMass::Foo::Bar" => [b.object_id]
175
+ }, Mass.index(Foo))
176
+ assert_equal({
177
+ "Unit::TestMass::Foo##{f2.object_id}" => ["@foo"],
178
+ "Unit::TestMass::Foo::Bar##{b.object_id}" => ["@fool"],
179
+ "Unit::TestMass::Thing##{t.object_id}" => ["@food"]
180
+ }, Mass.references(f1))
181
+
182
+ assert_equal(false, Mass.detach(f1, OneMoreThing, Thing){f1 = nil})
183
+
184
+ assert_equal({
185
+ "Unit::TestMass::Foo" => [object_id, f2.object_id].sort,
186
+ "Unit::TestMass::Foo::Bar" => [b.object_id]
187
+ }, Mass.index(Foo))
188
+ assert_equal({
189
+ "Unit::TestMass::Foo##{f2.object_id}" => ["@foo"],
190
+ "Unit::TestMass::Foo::Bar##{b.object_id}" => ["@fool"]
191
+ }, Mass.references(f1))
192
+
193
+ assert_equal(false, f1.detach(Foo::Bar){f1 = nil})
194
+
195
+ assert_equal({
196
+ "Unit::TestMass::Foo" => [object_id, f2.object_id].sort,
197
+ "Unit::TestMass::Foo::Bar" => [b.object_id]
198
+ }, Mass.index(Foo))
199
+ assert_equal({
200
+ "Unit::TestMass::Foo##{f2.object_id}" => ["@foo"]
201
+ }, Mass.references(f1))
202
+
203
+ assert_equal(true, Mass.detach(f1){f1 = nil})
204
+
205
+ Mass.gc!(f1)
206
+ assert_equal({
207
+ "Unit::TestMass::Foo" => [f2.object_id],
208
+ "Unit::TestMass::Foo::Bar" => [b.object_id]
209
+ }, Mass.index(Foo))
210
+ end
211
+ end
212
+
213
+ describe "using complex objects" do
214
+ it "should be able to detach objects" do
215
+ # TODO: although RubyMass is used within a very complex project using DataMapper write tests within a similar situation
216
+ end
217
+ end
218
+ end
219
+
220
+ end
221
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-mass
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Paul Engel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-06-11 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description: Introspect the Ruby Heap by indexing, counting, locating references to and detaching (in order to release) objects - optionally narrowing by namespace
17
+ email:
18
+ - paul.engel@holder.nl
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - .gitignore
27
+ - CHANGELOG.rdoc
28
+ - Gemfile
29
+ - MIT-LICENSE
30
+ - README.rdoc
31
+ - Rakefile
32
+ - VERSION
33
+ - lib/core_ext.rb
34
+ - lib/core_ext/object.rb
35
+ - lib/mass.rb
36
+ - lib/ruby-mass.rb
37
+ - lib/ruby-mass/version.rb
38
+ - ruby-mass.gemspec
39
+ - script/console
40
+ - test/test_helper.rb
41
+ - test/unit/test_mass.rb
42
+ homepage: https://github.com/archan937/ruby-objects
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.17
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Introspect the Ruby Heap by indexing, counting, locating references to and detaching (in order to release) objects - optionally narrowing by namespace
69
+ test_files:
70
+ - test/test_helper.rb
71
+ - test/unit/test_mass.rb