gigpark-rcov 0.8.6

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.
Files changed (51) hide show
  1. data/BLURB +111 -0
  2. data/LICENSE +53 -0
  3. data/Rakefile +78 -0
  4. data/THANKS +96 -0
  5. data/bin/rcov +508 -0
  6. data/doc/readme_for_api.markdown +22 -0
  7. data/doc/readme_for_emacs.markdown +52 -0
  8. data/doc/readme_for_rake.markdown +51 -0
  9. data/doc/readme_for_vim.markdown +34 -0
  10. data/editor-extensions/rcov.el +131 -0
  11. data/editor-extensions/rcov.vim +38 -0
  12. data/ext/rcovrt/1.8/callsite.c +216 -0
  13. data/ext/rcovrt/1.8/rcovrt.c +287 -0
  14. data/ext/rcovrt/1.9/callsite.c +234 -0
  15. data/ext/rcovrt/1.9/rcovrt.c +264 -0
  16. data/ext/rcovrt/extconf.rb +21 -0
  17. data/lib/rcov.rb +33 -0
  18. data/lib/rcov/call_site_analyzer.rb +225 -0
  19. data/lib/rcov/code_coverage_analyzer.rb +268 -0
  20. data/lib/rcov/coverage_info.rb +36 -0
  21. data/lib/rcov/differential_analyzer.rb +116 -0
  22. data/lib/rcov/file_statistics.rb +334 -0
  23. data/lib/rcov/formatters.rb +13 -0
  24. data/lib/rcov/formatters/base_formatter.rb +168 -0
  25. data/lib/rcov/formatters/failure_report.rb +15 -0
  26. data/lib/rcov/formatters/full_text_report.rb +48 -0
  27. data/lib/rcov/formatters/html_coverage.rb +244 -0
  28. data/lib/rcov/formatters/html_erb_template.rb +45 -0
  29. data/lib/rcov/formatters/text_coverage_diff.rb +193 -0
  30. data/lib/rcov/formatters/text_report.rb +32 -0
  31. data/lib/rcov/formatters/text_summary.rb +11 -0
  32. data/lib/rcov/lowlevel.rb +146 -0
  33. data/lib/rcov/rcovtask.rb +155 -0
  34. data/lib/rcov/templates/detail.html.erb +78 -0
  35. data/lib/rcov/templates/index.html.erb +76 -0
  36. data/lib/rcov/templates/screen.css +168 -0
  37. data/lib/rcov/version.rb +10 -0
  38. data/setup.rb +1588 -0
  39. data/test/assets/sample_01.rb +7 -0
  40. data/test/assets/sample_02.rb +5 -0
  41. data/test/assets/sample_03.rb +20 -0
  42. data/test/assets/sample_04.rb +10 -0
  43. data/test/assets/sample_05-new.rb +17 -0
  44. data/test/assets/sample_05-old.rb +13 -0
  45. data/test/assets/sample_05.rb +17 -0
  46. data/test/call_site_analyzer_test.rb +171 -0
  47. data/test/code_coverage_analyzer_test.rb +184 -0
  48. data/test/file_statistics_test.rb +471 -0
  49. data/test/functional_test.rb +89 -0
  50. data/test/turn_off_rcovrt.rb +4 -0
  51. metadata +109 -0
@@ -0,0 +1,264 @@
1
+ #include <ruby.h>
2
+ #include <ruby/st.h>
3
+ #include <stdlib.h>
4
+ #include <assert.h>
5
+
6
+ #define COVERAGE_DEBUG_EVENTS 0
7
+
8
+ #define RCOVRT_VERSION_MAJOR 2
9
+ #define RCOVRT_VERSION_MINOR 0
10
+ #define RCOVRT_VERSION_REV 0
11
+
12
+ static VALUE mRcov;
13
+ static VALUE mRCOV__;
14
+ static VALUE oSCRIPT_LINES__;
15
+ static ID id_cover;
16
+ static st_table* coverinfo = 0;
17
+ static char coverage_hook_set_p;
18
+
19
+ struct cov_array {
20
+ unsigned int len;
21
+ unsigned int *ptr;
22
+ };
23
+
24
+ static struct cov_array *cached_array = 0;
25
+ static char *cached_file = 0;
26
+
27
+ static struct cov_array * coverage_increase_counter_uncached(char *sourcefile, unsigned int sourceline, char mark_only) {
28
+ struct cov_array *carray = NULL;
29
+
30
+ if(sourcefile == NULL) {
31
+ /* "can't happen", just ignore and avoid segfault */
32
+ return NULL;
33
+ }
34
+ else if(!st_lookup(coverinfo, (st_data_t)sourcefile, (st_data_t*)&carray)) {
35
+ VALUE arr;
36
+
37
+ arr = rb_hash_aref(oSCRIPT_LINES__, rb_str_new2(sourcefile));
38
+ if(NIL_P(arr))
39
+ return 0;
40
+ rb_check_type(arr, T_ARRAY);
41
+ carray = calloc(1, sizeof(struct cov_array));
42
+ carray->ptr = calloc(RARRAY_LEN(arr), sizeof(unsigned int));
43
+ carray->len = RARRAY_LEN(arr);
44
+ st_insert(coverinfo, (st_data_t)strdup(sourcefile), (st_data_t) carray);
45
+ }
46
+ else {
47
+ /* recovered carray, sanity check */
48
+ assert(carray && "failed to create valid carray");
49
+ }
50
+
51
+ if(mark_only) {
52
+ if(!carray->ptr[sourceline])
53
+ carray->ptr[sourceline] = 1;
54
+ }
55
+ else {
56
+ if (carray && carray->len > sourceline) {
57
+ carray->ptr[sourceline]++;
58
+ }
59
+ }
60
+
61
+ return carray;
62
+ }
63
+
64
+ static void coverage_mark_caller() {
65
+ coverage_increase_counter_uncached(rb_sourcefile(), rb_sourceline(), 1);
66
+ }
67
+
68
+ static void coverage_increase_counter_cached(char *sourcefile, int sourceline) {
69
+ if(cached_file == sourcefile && cached_array && cached_array->len > sourceline) {
70
+ cached_array->ptr[sourceline]++;
71
+ return;
72
+ }
73
+ cached_file = sourcefile;
74
+ cached_array = coverage_increase_counter_uncached(sourcefile, sourceline, 0);
75
+ }
76
+
77
+ static void coverage_event_coverage_hook(rb_event_flag_t event, VALUE node, VALUE self, ID mid, VALUE klass) {
78
+ char *sourcefile;
79
+ unsigned int sourceline;
80
+ static unsigned int in_hook = 0;
81
+
82
+ if(in_hook) {
83
+ return;
84
+ }
85
+
86
+ in_hook++;
87
+
88
+ #if COVERAGE_DEBUG_EVENTS
89
+ do {
90
+ int status;
91
+ VALUE old_exception;
92
+ old_exception = rb_gv_get("$!");
93
+ rb_protect(rb_inspect, klass, &status);
94
+ if(!status) {
95
+ printf("EVENT: %d %s %s %s %d\n", event,
96
+ klass ? RSTRING(rb_inspect(klass))->ptr : "",
97
+ mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
98
+ : "unknown",
99
+ node ? node->nd_file : "", node ? nd_line(node) : 0);
100
+ }
101
+ else {
102
+ printf("EVENT: %d %s %s %d\n", event,
103
+ mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
104
+ : "unknown",
105
+ node ? node->nd_file : "", node ? nd_line(node) : 0);
106
+ }
107
+ rb_gv_set("$!", old_exception);
108
+ } while (0);
109
+ #endif
110
+
111
+ if(event & RUBY_EVENT_C_CALL) {
112
+ coverage_mark_caller();
113
+ }
114
+ if(event & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN | RUBY_EVENT_CLASS)) {
115
+ in_hook--;
116
+ return;
117
+ }
118
+
119
+ sourcefile = rb_sourcefile();
120
+ sourceline = rb_sourceline();
121
+
122
+ if (0 == sourceline || 0 == sourcefile) {
123
+ in_hook--;
124
+ return;
125
+ }
126
+
127
+ coverage_increase_counter_cached(sourcefile, sourceline);
128
+ if(event & RUBY_EVENT_CALL)
129
+ coverage_mark_caller();
130
+ in_hook--;
131
+ }
132
+
133
+ static VALUE cov_install_coverage_hook(VALUE self) {
134
+ if(!coverage_hook_set_p) {
135
+ if(!coverinfo)
136
+ coverinfo = st_init_strtable();
137
+ coverage_hook_set_p = 1;
138
+ /* TODO: allow C_CALL too, since it's supported already
139
+ * the overhead is around ~30%, tested on typo */
140
+ VALUE holder = 0;
141
+ rb_add_event_hook(coverage_event_coverage_hook,
142
+ RUBY_EVENT_ALL & ~RUBY_EVENT_C_CALL &
143
+ ~RUBY_EVENT_C_RETURN & ~RUBY_EVENT_CLASS, holder);
144
+ return Qtrue;
145
+ }
146
+ else
147
+ return Qfalse;
148
+ }
149
+
150
+ static int populate_cover(st_data_t key, st_data_t value, st_data_t cover) {
151
+ VALUE rcover;
152
+ VALUE rkey;
153
+ VALUE rval;
154
+ struct cov_array *carray;
155
+ unsigned int i;
156
+
157
+ rcover = (VALUE)cover;
158
+ carray = (struct cov_array *) value;
159
+ rkey = rb_str_new2((char*) key);
160
+ rval = rb_ary_new2(carray->len);
161
+ for(i = 0; i < carray->len; i++)
162
+ rb_ary_push(rval, UINT2NUM(carray->ptr[i]));
163
+
164
+ rb_hash_aset(rcover, rkey, rval);
165
+
166
+ return ST_CONTINUE;
167
+ }
168
+
169
+ static int free_table(st_data_t key, st_data_t value, st_data_t ignored) {
170
+ struct cov_array *carray;
171
+
172
+ carray = (struct cov_array *) value;
173
+ free((char *)key);
174
+ free(carray->ptr);
175
+ free(carray);
176
+
177
+ return ST_CONTINUE;
178
+ }
179
+
180
+ static VALUE cov_remove_coverage_hook(VALUE self) {
181
+ if(!coverage_hook_set_p)
182
+ return Qfalse;
183
+ else {
184
+ rb_remove_event_hook(coverage_event_coverage_hook);
185
+ coverage_hook_set_p = 0;
186
+ return Qtrue;
187
+ }
188
+ }
189
+
190
+ static VALUE cov_generate_coverage_info(VALUE self) {
191
+ VALUE cover;
192
+
193
+ if(rb_const_defined_at(mRCOV__, id_cover)) {
194
+ rb_mod_remove_const(mRCOV__, ID2SYM(id_cover));
195
+ }
196
+
197
+ cover = rb_hash_new();
198
+ if(coverinfo)
199
+ st_foreach(coverinfo, populate_cover, cover);
200
+ rb_define_const(mRCOV__, "COVER", cover);
201
+
202
+ return cover;
203
+ }
204
+
205
+ static VALUE cov_reset_coverage(VALUE self) {
206
+ if(coverage_hook_set_p) {
207
+ rb_raise(rb_eRuntimeError, "Cannot reset the coverage info in the middle of a traced run.");
208
+ return Qnil;
209
+ }
210
+
211
+ cached_array = 0;
212
+ cached_file = 0;
213
+ st_foreach(coverinfo, free_table, Qnil);
214
+ st_free_table(coverinfo);
215
+ coverinfo = 0;
216
+
217
+ return Qnil;
218
+ }
219
+
220
+ static VALUE cov_ABI(VALUE self) {
221
+ VALUE ret;
222
+
223
+ ret = rb_ary_new();
224
+ rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MAJOR));
225
+ rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MINOR));
226
+ rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_REV));
227
+
228
+ return ret;
229
+ }
230
+
231
+ void Init_rcovrt() {
232
+ ID id_rcov = rb_intern("Rcov");
233
+ ID id_coverage__ = rb_intern("RCOV__");
234
+ ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
235
+
236
+ id_cover = rb_intern("COVER");
237
+
238
+ if(rb_const_defined(rb_cObject, id_rcov))
239
+ mRcov = rb_const_get(rb_cObject, id_rcov);
240
+ else
241
+ mRcov = rb_define_module("Rcov");
242
+
243
+ if(rb_const_defined(mRcov, id_coverage__))
244
+ mRCOV__ = rb_const_get_at(mRcov, id_coverage__);
245
+ else
246
+ mRCOV__ = rb_define_module_under(mRcov, "RCOV__");
247
+
248
+ if(rb_const_defined(rb_cObject, id_script_lines__))
249
+ oSCRIPT_LINES__ = rb_const_get(rb_cObject, rb_intern("SCRIPT_LINES__"));
250
+ else {
251
+ oSCRIPT_LINES__ = rb_hash_new();
252
+ rb_const_set(rb_cObject, id_script_lines__, oSCRIPT_LINES__);
253
+ }
254
+
255
+ coverage_hook_set_p = 0;
256
+
257
+ rb_define_singleton_method(mRCOV__, "install_coverage_hook", cov_install_coverage_hook, 0);
258
+ rb_define_singleton_method(mRCOV__, "remove_coverage_hook", cov_remove_coverage_hook, 0);
259
+ rb_define_singleton_method(mRCOV__, "generate_coverage_info", cov_generate_coverage_info, 0);
260
+ rb_define_singleton_method(mRCOV__, "reset_coverage", cov_reset_coverage, 0);
261
+ rb_define_singleton_method(mRCOV__, "ABI", cov_ABI, 0);
262
+
263
+ Init_rcov_callsite();
264
+ }
@@ -0,0 +1,21 @@
1
+ require 'mkmf'
2
+
3
+ dir_config("gcov")
4
+ if ENV["USE_GCOV"] and Config::CONFIG['CC'] =~ /gcc/ and
5
+ have_library("gcov", "__gcov_open")
6
+
7
+ $CFLAGS << " -fprofile-arcs -ftest-coverage"
8
+ if RUBY_VERSION =~ /1.9/
9
+ $CFLAGS << ' -DRUBY_19_COMPATIBILITY'
10
+ create_makefile("rcovrt", "1.9/")
11
+ else
12
+ create_makefile("rcovrt", "1.8/")
13
+ end
14
+ else
15
+ if RUBY_VERSION =~ /1.9/
16
+ $CFLAGS << ' -DRUBY_19_COMPATIBILITY'
17
+ create_makefile("rcovrt", "1.9/")
18
+ else
19
+ create_makefile("rcovrt", "1.8/")
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ # rcov Copyright (c) 2004-2006 Mauricio Fernandez <mfp@acm.org>
2
+ #
3
+ # See LICENSE for licensing information.
4
+
5
+ # NOTE: if you're reading this in the XHTML code coverage report generated by
6
+ # rcov, you'll notice that only code inside methods is reported as covered,
7
+ # very much like what happens when you run it with --test-unit-only.
8
+ # This is due to the fact that we're running rcov on itself: the code below is
9
+ # already loaded before coverage tracing is activated, so only code inside
10
+ # methods is actually executed under rcov's inspection.
11
+
12
+ require 'rcov/version'
13
+ require 'rcov/formatters'
14
+ require 'rcov/coverage_info'
15
+ require 'rcov/file_statistics'
16
+ require 'rcov/differential_analyzer'
17
+ require 'rcov/code_coverage_analyzer'
18
+ require 'rcov/call_site_analyzer'
19
+
20
+ SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
21
+
22
+ module Rcov
23
+ # TODO: Move to Ruby 1.8.6 Backport module
24
+ unless RUBY_VERSION =~ /1.9/
25
+ class ::String
26
+ def lines
27
+ map
28
+ end
29
+ end
30
+ end
31
+
32
+ autoload :RCOV__, "rcov/lowlevel.rb"
33
+ end
@@ -0,0 +1,225 @@
1
+ module Rcov
2
+ # A CallSiteAnalyzer can be used to obtain information about:
3
+ # * where a method is defined ("+defsite+")
4
+ # * where a method was called from ("+callsite+")
5
+ #
6
+ # == Example
7
+ # <tt>example.rb</tt>:
8
+ # class X
9
+ # def f1; f2 end
10
+ # def f2; 1 + 1 end
11
+ # def f3; f1 end
12
+ # end
13
+ #
14
+ # analyzer = Rcov::CallSiteAnalyzer.new
15
+ # x = X.new
16
+ # analyzer.run_hooked do
17
+ # x.f1
18
+ # end
19
+ # # ....
20
+ #
21
+ # analyzer.run_hooked do
22
+ # x.f3
23
+ # # the information generated in this run is aggregated
24
+ # # to the previously recorded one
25
+ # end
26
+ #
27
+ # analyzer.analyzed_classes # => ["X", ... ]
28
+ # analyzer.methods_for_class("X") # => ["f1", "f2", "f3"]
29
+ # analyzer.defsite("X#f1") # => DefSite object
30
+ # analyzer.callsites("X#f2") # => hash with CallSite => count
31
+ # # associations
32
+ # defsite = analyzer.defsite("X#f1")
33
+ # defsite.file # => "example.rb"
34
+ # defsite.line # => 2
35
+ #
36
+ # You can have several CallSiteAnalyzer objects at a time, and it is
37
+ # possible to nest the #run_hooked / #install_hook/#remove_hook blocks: each
38
+ # analyzer will manage its data separately. Note however that no special
39
+ # provision is taken to ignore code executed "inside" the CallSiteAnalyzer
40
+ # class.
41
+ #
42
+ # +defsite+ information is only available for methods that were called under
43
+ # the inspection of the CallSiteAnalyzer, i.o.w. you will only have +defsite+
44
+ # information for those methods for which callsite information is
45
+ # available.
46
+ class CallSiteAnalyzer < DifferentialAnalyzer
47
+ # A method definition site.
48
+ class DefSite < Struct.new(:file, :line)
49
+ end
50
+
51
+ # Object representing a method call site.
52
+ # It corresponds to a part of the callstack starting from the context that
53
+ # called the method.
54
+ class CallSite < Struct.new(:backtrace)
55
+ # The depth of a CallSite is the number of stack frames
56
+ # whose information is included in the CallSite object.
57
+ def depth
58
+ backtrace.size
59
+ end
60
+
61
+ # File where the method call originated.
62
+ # Might return +nil+ or "" if it is not meaningful (C extensions, etc).
63
+ def file(level = 0)
64
+ stack_frame = backtrace[level]
65
+ stack_frame ? stack_frame[2] : nil
66
+ end
67
+
68
+ # Line where the method call originated.
69
+ # Might return +nil+ or 0 if it is not meaningful (C extensions, etc).
70
+ def line(level = 0)
71
+ stack_frame = backtrace[level]
72
+ stack_frame ? stack_frame[3] : nil
73
+ end
74
+
75
+ # Name of the method where the call originated.
76
+ # Returns +nil+ if the call originated in +toplevel+.
77
+ # Might return +nil+ if it could not be determined.
78
+ def calling_method(level = 0)
79
+ stack_frame = backtrace[level]
80
+ stack_frame ? stack_frame[1] : nil
81
+ end
82
+
83
+ # Name of the class holding the method where the call originated.
84
+ # Might return +nil+ if it could not be determined.
85
+ def calling_class(level = 0)
86
+ stack_frame = backtrace[level]
87
+ stack_frame ? stack_frame[0] : nil
88
+ end
89
+ end
90
+
91
+ @hook_level = 0
92
+ # defined this way instead of attr_accessor so that it's covered
93
+ def self.hook_level # :nodoc:
94
+ @hook_level
95
+ end
96
+
97
+ def self.hook_level=(x) # :nodoc:
98
+ @hook_level = x
99
+ end
100
+
101
+ def initialize
102
+ super(:install_callsite_hook, :remove_callsite_hook,
103
+ :reset_callsite)
104
+ end
105
+
106
+ # Classes whose methods have been called.
107
+ # Returns an array of strings describing the classes (just klass.to_s for
108
+ # each of them). Singleton classes are rendered as:
109
+ # #<Class:MyNamespace::MyClass>
110
+ def analyzed_classes
111
+ raw_data_relative.first.keys.map{|klass, meth| klass}.uniq.sort
112
+ end
113
+
114
+ # Methods that were called for the given class. See #analyzed_classes for
115
+ # the notation used for singleton classes.
116
+ # Returns an array of strings or +nil+
117
+ def methods_for_class(classname)
118
+ a = raw_data_relative.first.keys.select{|kl,_| kl == classname}.map{|_,meth| meth}.sort
119
+ a.empty? ? nil : a
120
+ end
121
+ alias_method :analyzed_methods, :methods_for_class
122
+
123
+ # Returns a hash with <tt>CallSite => call count</tt> associations or +nil+
124
+ # Can be called in two ways:
125
+ # analyzer.callsites("Foo#f1") # instance method
126
+ # analyzer.callsites("Foo.g1") # singleton method of the class
127
+ # or
128
+ # analyzer.callsites("Foo", "f1")
129
+ # analyzer.callsites("#<class:Foo>", "g1")
130
+ def callsites(classname_or_fullname, methodname = nil)
131
+ rawsites = raw_data_relative.first[expand_name(classname_or_fullname, methodname)]
132
+ return nil unless rawsites
133
+ ret = {}
134
+ # could be a job for inject but it's slow and I don't mind the extra loc
135
+ rawsites.each_pair do |backtrace, count|
136
+ ret[CallSite.new(backtrace)] = count
137
+ end
138
+ ret
139
+ end
140
+
141
+ # Returns a DefSite object corresponding to the given method
142
+ # Can be called in two ways:
143
+ # analyzer.defsite("Foo#f1") # instance method
144
+ # analyzer.defsite("Foo.g1") # singleton method of the class
145
+ # or
146
+ # analyzer.defsite("Foo", "f1")
147
+ # analyzer.defsite("#<class:Foo>", "g1")
148
+ def defsite(classname_or_fullname, methodname = nil)
149
+ file, line = raw_data_relative[1][expand_name(classname_or_fullname, methodname)]
150
+ return nil unless file && line
151
+ DefSite.new(file, line)
152
+ end
153
+
154
+ private
155
+
156
+ def expand_name(classname_or_fullname, methodname = nil)
157
+ if methodname.nil?
158
+ case classname_or_fullname
159
+ when /(.*)#(.*)/ then classname, methodname = $1, $2
160
+ when /(.*)\.(.*)/ then classname, methodname = "#<Class:#{$1}>", $2
161
+ else
162
+ raise ArgumentError, "Incorrect method name"
163
+ end
164
+
165
+ return [classname, methodname]
166
+ end
167
+
168
+ [classname_or_fullname, methodname]
169
+ end
170
+
171
+ def data_default; [{}, {}] end
172
+
173
+ def raw_data_absolute
174
+ raw, method_def_site = RCOV__.generate_callsite_info
175
+ ret1 = {}
176
+ ret2 = {}
177
+ raw.each_pair do |(klass, method), hash|
178
+ begin
179
+ key = [klass.to_s, method.to_s]
180
+ ret1[key] = hash.clone #Marshal.load(Marshal.dump(hash))
181
+ ret2[key] = method_def_site[[klass, method]]
182
+ #rescue Exception
183
+ end
184
+ end
185
+
186
+ [ret1, ret2]
187
+ end
188
+
189
+ def aggregate_data(aggregated_data, delta)
190
+ callsites1, defsites1 = aggregated_data
191
+ callsites2, defsites2 = delta
192
+
193
+ callsites2.each_pair do |(klass, method), hash|
194
+ dest_hash = (callsites1[[klass, method]] ||= {})
195
+ hash.each_pair do |callsite, count|
196
+ dest_hash[callsite] ||= 0
197
+ dest_hash[callsite] += count
198
+ end
199
+ end
200
+
201
+ defsites1.update(defsites2)
202
+ end
203
+
204
+ def compute_raw_data_difference(first, last)
205
+ difference = {}
206
+ default = Hash.new(0)
207
+
208
+ callsites1, defsites1 = *first
209
+ callsites2, defsites2 = *last
210
+
211
+ callsites2.each_pair do |(klass, method), hash|
212
+ old_hash = callsites1[[klass, method]] || default
213
+ hash.each_pair do |callsite, count|
214
+ diff = hash[callsite] - (old_hash[callsite] || 0)
215
+ if diff > 0
216
+ difference[[klass, method]] ||= {}
217
+ difference[[klass, method]][callsite] = diff
218
+ end
219
+ end
220
+ end
221
+
222
+ [difference, defsites1.update(defsites2)]
223
+ end
224
+ end
225
+ end