cache_method 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ 0.2.4 / 2012-04-17
2
+
3
+ * Enhancements
4
+
5
+ * Refactored to simplify and make thread-safe
6
+
1
7
  0.2.3 / 2012-04-06
2
8
 
3
9
  * Enhancements
data/Gemfile CHANGED
@@ -7,3 +7,5 @@ gemspec
7
7
  gem 'rake'
8
8
  gem 'dalli'
9
9
  gem 'yard'
10
+ gem 'minitest'
11
+ gem 'minitest-reporters'
data/README.markdown CHANGED
@@ -6,28 +6,35 @@ Lets you cache the results of calling methods given their arguments. Like memoiz
6
6
 
7
7
  ## Real-world usage
8
8
 
9
- In production use at [impact.brighterplanet.com](http://impact.brighterplanet.com) and [data.brighterplanet.com](http://data.brighterplanet.com).
9
+ <p><a href="http://brighterplanet.com"><img src="https://s3.amazonaws.com/static.brighterplanet.com/assets/logos/flush-left/inline/green/rasterized/brighter_planet-160-transparent.png" alt="Brighter Planet logo"/></a></p>
10
+
11
+ We use `cache_method` for [data science at Brighter Planet](http://brighterplanet.com/research) and in production at
12
+
13
+ * [Brighter Planet's impact estimate web service](http://impact.brighterplanet.com)
14
+ * [Brighter Planet's reference data web service](http://data.brighterplanet.com)
15
+
16
+ ## Rationale
17
+
18
+ * It should be easy to cache instance methods
19
+ * It should be easy to cache methods that depend on object state
20
+ * It should be easy to uncache a method without clearing the whole cache
21
+ * It should be easy to do all that using a default in-process store, memcached, dalli (if you're on heroku), redis, etc. (all supported by this gem through the [cache gem](https://rubygems.org/gems/cache))
10
22
 
11
23
  ## Example
12
24
 
13
25
  require 'cache_method'
14
26
  class Blog
15
- attr_reader :name
16
- attr_reader :url
17
-
27
+ attr_reader :name, :url
18
28
  def initialize(name, url)
19
29
  @name = name
20
30
  @url = url
21
31
  end
22
-
23
- def get_latest_entries
32
+ # The slow method that you want to speed up
33
+ def entries(date)
24
34
  # ...
25
35
  end
26
- cache_method :get_latest_entries
27
-
28
- # By default, cache_method derives the cache key for an instance by getting the SHA1 hash of the Marshal.dump
29
- # If you need to customize how an instance is recognized, you can define #as_cache_key.
30
- # Marshal.load will be called on the result.
36
+ cache_method :entries
37
+ # Not always required
31
38
  def as_cache_key
32
39
  { :name => name, :url => url }
33
40
  end
@@ -35,37 +42,20 @@ In production use at [impact.brighterplanet.com](http://impact.brighterplanet.co
35
42
 
36
43
  Then you can do
37
44
 
38
- my_blog.get_latest_entries => first time won't be cached
39
- my_blog.get_latest_entries => second time will come from cache
45
+ my_blog.entries(Date.today) => first time won't be cached
46
+ my_blog.entries(Date.today) => second time will come from cache
40
47
 
41
48
  And clear them too
42
49
 
43
- my_blog.cache_method_clear :get_latest_entries
50
+ my_blog.cache_method_clear :entries
44
51
 
45
52
  (which doesn't delete the rest of your cache)
46
53
 
47
- ## ActiveRecord
48
-
49
- If you're caching methods ActiveRecord objects (aka instances of `ActiveRecord::Base`), then you should probably define something like:
50
-
51
- class ActiveRecord::Base
52
- def as_cache_key
53
- attributes
54
- end
55
- end
56
-
57
- ## Debug
58
-
59
- CacheMethod can warn you if your obj or args cache keys are suspiciously long.
60
-
61
- require 'cache_method'
62
- require 'cache_method/debug'
63
-
64
- Then watch your logs.
65
-
66
54
  ## Configuration (and supported cache clients)
67
55
 
68
- You need to set where the cache will be stored:
56
+ By default, an in-process, non-shared cache is used.
57
+
58
+ You can set where the cache will be stored:
69
59
 
70
60
  CacheMethod.config.storage = Memcached.new '127.0.0.1:11211'
71
61
 
@@ -79,13 +69,30 @@ or this might even work...
79
69
 
80
70
  See `Config` for the full list of supported caches.
81
71
 
82
- ## Defining a #as_cache_key method
72
+ ## Cache keys
73
+
74
+ Caching a method involves getting cache keys for
75
+
76
+ 1. the object where the method is defined - for example, `my_blog.as_cache_key`
77
+ 2. the arguments passed to the method - for example, `Marshal.dump(Date.today)`, because `Date#as_cache_key` is not defined
78
+
79
+ Then the cache keys are SHA-1 hashed and combined for an overall key:
80
+
81
+ method signature + obj digest + args digest
82
+ Blog#entries + SHA1(Marshal.dump({:name="Seamus's blog",:url=>"http://numbers.brighterplanet.com"}) + SHA1(Marshal.dump(Date.today))
83
+
84
+ Technically the full cache key is
85
+
86
+ # when caching class methods
87
+ method signature + generation + args digest
88
+ # when caching instance methods
89
+ method signature + obj digest + generation + args digest
83
90
 
84
- Since we're not pure functional programmers, sometimes cache hits depend on object state in addition to method arguments. To illustrate:
91
+ (see "Generational caching" below for an explanation of the generation part)
85
92
 
86
- my_blog.get_latest_entries
93
+ ### #as_cache_key
87
94
 
88
- get_latest_entries doesn't take any arguments, so it must depend on my_blog.url or something. This works because we define:
95
+ As above, you can define a custom cache key for an object:
89
96
 
90
97
  class Blog
91
98
  # [...]
@@ -95,26 +102,97 @@ get_latest_entries doesn't take any arguments, so it must depend on my_blog.url
95
102
  # [...]
96
103
  end
97
104
 
98
- If you don't define `#as_cache_key`, then `cache_method` will `Marshal.dump` an instance.
105
+ If you don't define `#as_cache_key`, the default is to `Marshal.dump` the whole object. (That's not as terrible as it seems - marshalling is fast!)
99
106
 
100
- ## Danger: #to_cache_key
107
+ ### #to_cache_key (danger!)
101
108
 
102
- Let's say you want need cache keys from classes that undefine the `#class` method (how annoying). You can define `#to_cache_key`, but **make sure it identifies the class too!** (in addition to the instance.)
109
+ There's another way to define a cache key, but it should be used with caution because it gives you total control.
103
110
 
104
- For example, if you find yourself passing association proxies as arguments to cached methods, this might be helpful:
111
+ The key is to make sure your `#to_cache_key` method identifies both the class and the instance!
105
112
 
106
- user = User.first
107
- Foo.bar(user.groups) # you're passing an association as an argument
113
+ ### Comparison
108
114
 
115
+ <table>
116
+ <tr>
117
+ <th>Method</th>
118
+ <th>Must uniquely identify class</th>
119
+ <th>Must uniquely identify instance</th>
120
+ </tr>
121
+ <tr>
122
+ <td><code>#as_cache_key</code></td>
123
+ <td>N&dagger;</td>
124
+ <td>Y</td>
125
+ </tr>
126
+ <tr>
127
+ <td><code>#to_cache_key</code></td>
128
+ <td>Y</td>
129
+ <td>Y</td>
130
+ </tr>
131
+ </table>
132
+
133
+ &dagger; The class name is automatically inserted for you by calling `object.class.name`, which is what causes all the trouble with `ActiveRecord::Associations::CollectionProxy`, etc.
134
+
135
+ ## ActiveRecord
136
+
137
+ If you're caching methods on instances of `ActiveRecord::Base`, and/or using them as arguments to other cached methods, then you should probably define something like:
138
+
139
+ class ActiveRecord::Base
140
+ def as_cache_key
141
+ attributes
142
+ end
143
+ end
144
+
145
+ If you find yourself passing association proxies as arguments to cached methods, this might be helpful:
146
+
147
+ # For use in ActiveRecord 3.0.x
109
148
  class ActiveRecord::Associations::AssociationCollection
110
- # danger! this is a special case... try to use #as_cache_key instead
111
- # also note that this example is based on ActiveRecord 3.0.10 ... YMMV
149
+ # rare use of to_cache_key
112
150
  def to_cache_key
113
- [ proxy_owner.class.name, proxy_owner.id, proxy_reflection.name, conditions ].join('/')
151
+ [
152
+ 'ActiveRecord::Associations::AssociationCollection',
153
+ proxy_owner.class.name,
154
+ proxy_owner.id,
155
+ proxy_reflection.name,
156
+ conditions
157
+ ].join('/')
114
158
  end
115
159
  end
116
160
 
117
- Otherwise, `cache_method` will try to run `user.groups.class` which causes the proxy to be loaded. You probably don't want to load 1000 AR objects just to generate a cache key.
161
+ # For use in ActiveRecord 3.2.x
162
+ class ActiveRecord::Associations::CollectionProxy
163
+ # rare use of to_cache_key
164
+ def to_cache_key
165
+ owner = proxy_association.owner
166
+ [
167
+ 'ActiveRecord::Associations::CollectionProxy', # [included because we're using #to_cache_key instead of #as_cache_key ]
168
+ owner.class.name, # User
169
+ owner.id, # 'seamusabshere'
170
+ proxy_association.reflection.name, # :comments
171
+ scoped.where_sql # "WHERE `comments`.`user_id` = 'seamusabshere'" [maybe a little bit redundant, but play it safe]
172
+ ].join('/')
173
+ end
174
+ end
175
+
176
+ Otherwise, `cache_method` will call `user.comments.class.name` which causes the proxy to load the target, i.e. load all the Comment objects into memory. You probably don't want to load 1000 AR objects just to generate a cache key.
177
+
178
+ ## Generational caching
179
+
180
+ Generational caching allows clearing the cached results for only one method, for example
181
+
182
+ my_blog.cache_method_clear :entries
183
+
184
+ You can disable it to get a little speed boost
185
+
186
+ CacheMethod.config.generational = false
187
+
188
+ ## Debug
189
+
190
+ CacheMethod can warn you if your obj or args cache keys are suspiciously long.
191
+
192
+ require 'cache_method'
193
+ require 'cache_method/debug'
194
+
195
+ Then watch your logs.
118
196
 
119
197
  ## Module methods
120
198
 
@@ -143,13 +221,6 @@ Rest assured that `Tiger.my_module_method` and `Lion.my_module_method` will be c
143
221
  # cache_method :my_module_method
144
222
  end
145
223
 
146
- ## Rationale
147
-
148
- * It should be easy to cache a method using memcached, dalli (if you're on heroku), redis, etc. (that's why I made the [cache gem](https://rubygems.org/gems/cache))
149
- * It should be easy to uncache a method without clearing the whole cache
150
- * It should be easy to cache instance methods
151
- * It should be easy to cache methods that depend on object state (hence `#as_cache_key`)
152
-
153
224
  ## Copyright
154
225
 
155
226
  Copyright 2012 Seamus Abshere
@@ -1,91 +1,66 @@
1
- require 'digest/sha1'
2
1
  module CacheMethod
3
2
  class CachedResult #:nodoc: all
4
- class << self
5
- def resolve_cache_key(obj)
6
- case obj
7
- when ::Array
8
- obj.map do |v|
9
- resolve_cache_key v
10
- end
11
- when ::Hash
12
- obj.inject({}) do |memo, (k, v)|
13
- kk = resolve_cache_key k
14
- vv = resolve_cache_key v
15
- memo[kk] = vv
16
- memo
17
- end
18
- else
19
- if obj.respond_to?(:to_cache_key)
20
- # this is meant to be used sparingly, usually when a proxy class is involved
21
- obj.to_cache_key
22
- elsif obj.respond_to?(:as_cache_key)
23
- [obj.class.name, obj.as_cache_key]
24
- else
25
- obj
26
- end
27
- end
28
- end
29
- end
30
-
31
- CACHE_KEY_JOINER = ','
32
-
33
- def initialize(obj, method_id, original_method_id, ttl, args)
3
+ def initialize(obj, method_id, original_method_id, ttl, args, &blk)
34
4
  @obj = obj
35
5
  @method_id = method_id
6
+ @method_signature = CacheMethod.method_signature obj, method_id
36
7
  @original_method_id = original_method_id
37
- @ttl = ttl
8
+ @ttl = ttl || CacheMethod.config.default_ttl
38
9
  @args = args
10
+ @args_digest = args.empty? ? 'empty' : CacheMethod.digest(args)
11
+ @blk = blk
12
+ @fetch_mutex = ::Mutex.new
39
13
  end
40
14
 
41
15
  attr_reader :obj
42
16
  attr_reader :method_id
17
+ attr_reader :method_signature
43
18
  attr_reader :original_method_id
44
19
  attr_reader :args
20
+ attr_reader :args_digest
21
+ attr_reader :blk
22
+ attr_reader :ttl
45
23
 
46
24
  # Store things wrapped in an Array so that nil is accepted
47
25
  def fetch
48
- if v = Config.instance.storage.get(cache_key) and v.is_a?(::Array)
49
- v.first
26
+ if wrapped_v = get_wrapped
27
+ wrapped_v.first
50
28
  else
51
- v = obj.send original_method_id, *args
52
- Config.instance.storage.set cache_key, [v], ttl
53
- v
29
+ @fetch_mutex.synchronize do
30
+ (get_wrapped || set_wrapped).first
31
+ end
54
32
  end
55
33
  end
56
34
 
57
35
  def exist?
58
- Config.instance.storage.exist?(cache_key)
36
+ CacheMethod.config.storage.exist?(cache_key)
59
37
  end
60
-
61
- def ttl
62
- @ttl ||= Config.instance.default_ttl
63
- end
64
-
38
+
39
+ private
40
+
65
41
  def cache_key
66
42
  if obj.is_a?(::Class) or obj.is_a?(::Module)
67
43
  [ 'CacheMethod', 'CachedResult', method_signature, current_generation, args_digest ].compact.join CACHE_KEY_JOINER
68
44
  else
69
- [ 'CacheMethod', 'CachedResult', method_signature, obj_digest, current_generation, args_digest ].compact.join CACHE_KEY_JOINER
45
+ [ 'CacheMethod', 'CachedResult', method_signature, CacheMethod.digest(obj), current_generation, args_digest ].compact.join CACHE_KEY_JOINER
70
46
  end
71
47
  end
72
-
73
- def method_signature
74
- @method_signature ||= ::CacheMethod.method_signature(obj, method_id)
75
- end
76
-
77
- def obj_digest
78
- @obj_digest ||= ::Digest::SHA1.hexdigest(::Marshal.dump(CachedResult.resolve_cache_key(obj)))
79
- end
80
-
81
- def args_digest
82
- @args_digest ||= args.empty? ? 'empty' : ::Digest::SHA1.hexdigest(::Marshal.dump(CachedResult.resolve_cache_key(args)))
83
- end
84
-
48
+
85
49
  def current_generation
86
- if Config.instance.generational?
87
- @current_generation ||= Generation.new(obj, method_id).current
50
+ if CacheMethod.config.generational?
51
+ Generation.new(obj, method_id).fetch
88
52
  end
89
53
  end
54
+
55
+ def get_wrapped
56
+ CacheMethod.config.storage.get cache_key
57
+ end
58
+
59
+ def set_wrapped
60
+ v = obj.send(*([original_method_id]+args), &blk)
61
+ wrapped_v = [v]
62
+ CacheMethod.config.storage.set cache_key, wrapped_v, ttl
63
+ wrapped_v
64
+ end
90
65
  end
91
66
  end
@@ -1,5 +1,5 @@
1
1
  require 'cache'
2
- require 'singleton'
2
+
3
3
  module CacheMethod
4
4
  # Here's where you set config options.
5
5
  #
@@ -9,7 +9,10 @@ module CacheMethod
9
9
  #
10
10
  # You'd probably put this in your Rails config/initializers, for example.
11
11
  class Config
12
- include ::Singleton
12
+
13
+ def initialize
14
+ @mutex = ::Mutex.new
15
+ end
13
16
 
14
17
  # Whether to use "generational" caching. Default is true.
15
18
  #
@@ -44,7 +47,9 @@ module CacheMethod
44
47
  end
45
48
 
46
49
  def storage #:nodoc:
47
- @storage ||= ::Cache.new
50
+ @storage || @mutex.synchronize do
51
+ @storage ||= ::Cache.new
52
+ end
48
53
  end
49
54
 
50
55
  # TTL for method caches. Defaults to 24 hours or 86,400 seconds.
@@ -1,17 +1,8 @@
1
1
  module CacheMethod
2
- class CachedResult #:nodoc: all
3
- def obj_digest
4
- if (l = ::Marshal.dump(CachedResult.resolve_cache_key(obj)).length) > 500
5
- $stderr.puts "Warn: #{l} long obj digest. #{obj.class} -> #{CachedResult.resolve_cache_key(obj).inspect}"
6
- end
7
- @obj_digest ||= ::Digest::SHA1.hexdigest(::Marshal.dump(CachedResult.resolve_cache_key(obj)))
8
- end
9
-
10
- def args_digest
11
- if (l = ::Marshal.dump(CachedResult.resolve_cache_key(args)).length) > 500
12
- $stderr.puts "Warn: #{l} long args digest. #{method_signature}(#{CachedResult.resolve_cache_key(args).inspect})"
13
- end
14
- @args_digest ||= args.empty? ? 'empty' : ::Digest::SHA1.hexdigest(::Marshal.dump(CachedResult.resolve_cache_key(args)))
2
+ def CacheMethod.digest(obj)
3
+ if (l = ::Marshal.dump(resolve_cache_key(obj)).length) > 500
4
+ $stderr.puts "[cache_method] Warn: #{l} long digest. #{obj.class} -> #{resolve_cache_key(obj).inspect}"
15
5
  end
6
+ ::Digest::SHA1.hexdigest ::Marshal.dump(resolve_cache_key(obj))
16
7
  end
17
8
  end
@@ -1,49 +1,49 @@
1
- require 'digest/sha1'
2
1
  module CacheMethod
3
2
  class Generation #:nodoc: all
4
- class << self
5
- def random_name
6
- rand(100_000_000).to_s
7
- end
8
- end
9
-
10
3
  def initialize(obj, method_id)
11
4
  @obj = obj
12
5
  @method_id = method_id
6
+ @method_signature = CacheMethod.method_signature obj, method_id
7
+ @fetch_mutex = ::Mutex.new
13
8
  end
14
9
 
15
10
  attr_reader :obj
16
11
  attr_reader :method_id
17
-
18
- def method_signature
19
- @method_signature ||= ::CacheMethod.method_signature(obj, method_id)
12
+ attr_reader :method_signature
13
+
14
+ def fetch
15
+ if existing = get
16
+ existing
17
+ else
18
+ @fetch_mutex.synchronize do
19
+ get || set
20
+ end
21
+ end
20
22
  end
21
23
 
22
- def obj_digest
23
- @obj_digest ||= ::Digest::SHA1.hexdigest(::Marshal.dump(obj.respond_to?(:as_cache_key) ? obj.as_cache_key : obj))
24
+ def mark_passing
25
+ CacheMethod.config.storage.delete cache_key
24
26
  end
25
-
27
+
28
+ private
29
+
26
30
  def cache_key
27
31
  if obj.is_a?(::Class) or obj.is_a?(::Module)
28
- [ 'CacheMethod', 'Generation', method_signature ].join ','
32
+ [ 'CacheMethod', 'Generation', method_signature ].join CACHE_KEY_JOINER
29
33
  else
30
- [ 'CacheMethod', 'Generation', method_signature, obj_digest ].join ','
34
+ [ 'CacheMethod', 'Generation', method_signature, CacheMethod.digest(obj) ].join CACHE_KEY_JOINER
31
35
  end
32
36
  end
33
-
34
- def current
35
- if cached_v = Config.instance.storage.get(cache_key)
36
- cached_v
37
- else
38
- v = Generation.random_name
39
- # never expire!
40
- Config.instance.storage.set cache_key, v, 0
41
- v
42
- end
37
+
38
+ def get
39
+ CacheMethod.config.storage.get cache_key
43
40
  end
44
-
45
- def mark_passing
46
- Config.instance.storage.delete cache_key
41
+
42
+ def set
43
+ random_name = ::Kernel.rand(1e11).to_s
44
+ # never expire!
45
+ CacheMethod.config.storage.set cache_key, random_name, 0
46
+ random_name
47
47
  end
48
48
  end
49
49
  end
@@ -1,3 +1,3 @@
1
1
  module CacheMethod
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
3
3
  end
data/lib/cache_method.rb CHANGED
@@ -1,24 +1,60 @@
1
+ require 'thread'
2
+ require 'digest/sha1'
3
+
1
4
  require 'cache_method/config'
2
5
  require 'cache_method/cached_result'
3
6
  require 'cache_method/generation'
4
7
 
5
- # See the README.rdoc for more info!
6
8
  module CacheMethod
7
- def self.config #:nodoc:
8
- Config.instance
9
+ MUTEX = ::Mutex.new
10
+ CACHE_KEY_JOINER = ','
11
+
12
+ def CacheMethod.config #:nodoc:
13
+ @config || MUTEX.synchronize do
14
+ @config ||= Config.new
15
+ end
9
16
  end
10
17
 
11
- def self.klass_name(obj) #:nodoc:
18
+ def CacheMethod.klass_name(obj) #:nodoc:
12
19
  (obj.is_a?(::Class) or obj.is_a?(::Module)) ? obj.to_s : obj.class.to_s
13
20
  end
14
21
 
15
- def self.method_delimiter(obj) #:nodoc:
22
+ def CacheMethod.method_delimiter(obj) #:nodoc:
16
23
  (obj.is_a?(::Class) or obj.is_a?(::Module)) ? '.' : '#'
17
24
  end
18
25
 
19
- def self.method_signature(obj, method_id) #:nodoc:
26
+ def CacheMethod.method_signature(obj, method_id) #:nodoc:
20
27
  [ klass_name(obj), method_id ].join method_delimiter(obj)
21
28
  end
29
+
30
+ def CacheMethod.resolve_cache_key(obj)
31
+ case obj
32
+ when ::Array
33
+ obj.map do |v|
34
+ resolve_cache_key v
35
+ end
36
+ when ::Hash
37
+ obj.inject({}) do |memo, (k, v)|
38
+ kk = resolve_cache_key k
39
+ vv = resolve_cache_key v
40
+ memo[kk] = vv
41
+ memo
42
+ end
43
+ else
44
+ if obj.respond_to?(:to_cache_key)
45
+ # this is meant to be used sparingly, usually when a proxy class is involved
46
+ obj.to_cache_key
47
+ elsif obj.respond_to?(:as_cache_key)
48
+ [obj.class.name, obj.as_cache_key]
49
+ else
50
+ obj
51
+ end
52
+ end
53
+ end
54
+
55
+ def CacheMethod.digest(obj)
56
+ ::Digest::SHA1.hexdigest ::Marshal.dump(resolve_cache_key(obj))
57
+ end
22
58
 
23
59
  # All Objects, including instances and Classes, get the <tt>#cache_method_clear</tt> method.
24
60
  module InstanceMethods
@@ -59,15 +95,13 @@ module CacheMethod
59
95
  def cache_method(method_id, ttl = nil)
60
96
  original_method_id = "_uncached_#{method_id}"
61
97
  alias_method original_method_id, method_id
62
- define_method method_id do |*args|
63
- ::CacheMethod::CachedResult.new(self, method_id, original_method_id, ttl, args).fetch
98
+ define_method method_id do |*args, &blk|
99
+ ::CacheMethod::CachedResult.new(self, method_id, original_method_id, ttl, args, &blk).fetch
64
100
  end
65
101
  end
66
102
  end
67
103
  end
68
104
 
69
- unless ::Object.method_defined? :cache_method
70
- ::Object.send :include, ::CacheMethod::InstanceMethods
71
- ::Class.send :include, ::CacheMethod::ClassMethods
72
- ::Module.send :include, ::CacheMethod::ClassMethods
73
- end
105
+ ::Object.send :include, ::CacheMethod::InstanceMethods
106
+ ::Class.send :include, ::CacheMethod::ClassMethods
107
+ ::Module.send :include, ::CacheMethod::ClassMethods
data/test/helper.rb CHANGED
@@ -1,19 +1,17 @@
1
1
  require 'rubygems'
2
- require 'bundler'
3
- Bundler.setup
4
- require 'test/unit'
2
+ require 'bundler/setup'
3
+ require 'minitest/spec'
4
+ require 'minitest/autorun'
5
+ require 'minitest/reporters'
6
+ MiniTest::Unit.runner = MiniTest::SuiteRunner.new
7
+ MiniTest::Unit.runner.reporters << MiniTest::Reporters::SpecReporter.new
5
8
 
6
9
  if ::Bundler.definition.specs['ruby-debug19'].first or ::Bundler.definition.specs['ruby-debug'].first
7
10
  require 'ruby-debug'
8
11
  end
9
12
 
10
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
11
- $LOAD_PATH.unshift(File.dirname(__FILE__))
12
13
  require 'cache_method'
13
14
 
14
- class Test::Unit::TestCase
15
- end
16
-
17
15
  class CopyCat1
18
16
  attr_reader :name
19
17
  def initialize(name)