relevance-rcov 0.9.3-java
Sign up to get free protection for your applications and to get access to all the features.
- data/BLURB +111 -0
- data/LICENSE +53 -0
- data/Rakefile +94 -0
- data/THANKS +110 -0
- data/bin/rcov +514 -0
- data/doc/readme_for_api.markdown +22 -0
- data/doc/readme_for_emacs.markdown +52 -0
- data/doc/readme_for_rake.markdown +51 -0
- data/doc/readme_for_vim.markdown +34 -0
- data/editor-extensions/rcov.el +131 -0
- data/editor-extensions/rcov.vim +38 -0
- data/ext/java/src/CallsiteHook.java +137 -0
- data/ext/java/src/CoverageHook.java +117 -0
- data/ext/java/src/RcovHook.java +9 -0
- data/ext/java/src/RcovrtService.java +130 -0
- data/lib/rcov.rb +33 -0
- data/lib/rcov/call_site_analyzer.rb +225 -0
- data/lib/rcov/code_coverage_analyzer.rb +268 -0
- data/lib/rcov/coverage_info.rb +36 -0
- data/lib/rcov/differential_analyzer.rb +116 -0
- data/lib/rcov/file_statistics.rb +334 -0
- data/lib/rcov/formatters.rb +13 -0
- data/lib/rcov/formatters/base_formatter.rb +173 -0
- data/lib/rcov/formatters/failure_report.rb +15 -0
- data/lib/rcov/formatters/full_text_report.rb +48 -0
- data/lib/rcov/formatters/html_coverage.rb +274 -0
- data/lib/rcov/formatters/html_erb_template.rb +62 -0
- data/lib/rcov/formatters/text_coverage_diff.rb +193 -0
- data/lib/rcov/formatters/text_report.rb +32 -0
- data/lib/rcov/formatters/text_summary.rb +11 -0
- data/lib/rcov/lowlevel.rb +146 -0
- data/lib/rcov/rcovtask.rb +155 -0
- data/lib/rcov/templates/detail.html.erb +64 -0
- data/lib/rcov/templates/index.html.erb +93 -0
- data/lib/rcov/templates/jquery-1.3.2.min.js +19 -0
- data/lib/rcov/templates/jquery.tablesorter.min.js +15 -0
- data/lib/rcov/templates/print.css +12 -0
- data/lib/rcov/templates/rcov.js +42 -0
- data/lib/rcov/templates/screen.css +270 -0
- data/lib/rcov/version.rb +10 -0
- data/lib/rcovrt.jar +0 -0
- data/setup.rb +1588 -0
- data/test/assets/sample_01.rb +7 -0
- data/test/assets/sample_02.rb +5 -0
- data/test/assets/sample_03.rb +20 -0
- data/test/assets/sample_04.rb +10 -0
- data/test/assets/sample_05-new.rb +17 -0
- data/test/assets/sample_05-old.rb +13 -0
- data/test/assets/sample_05.rb +17 -0
- data/test/assets/sample_06.rb +8 -0
- data/test/call_site_analyzer_test.rb +171 -0
- data/test/code_coverage_analyzer_test.rb +219 -0
- data/test/file_statistics_test.rb +471 -0
- data/test/functional_test.rb +91 -0
- data/test/turn_off_rcovrt.rb +4 -0
- metadata +115 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
import org.jruby.Ruby;
|
2
|
+
import org.jruby.RubyArray;
|
3
|
+
import org.jruby.RubyHash;
|
4
|
+
import org.jruby.runtime.ThreadContext;
|
5
|
+
import org.jruby.runtime.RubyEvent;
|
6
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
7
|
+
|
8
|
+
public class CoverageHook extends RcovHook {
|
9
|
+
|
10
|
+
private static CoverageHook hook;
|
11
|
+
|
12
|
+
public static CoverageHook getCoverageHook() {
|
13
|
+
if (hook == null) {
|
14
|
+
hook = new CoverageHook();
|
15
|
+
}
|
16
|
+
|
17
|
+
return hook;
|
18
|
+
}
|
19
|
+
|
20
|
+
private boolean active;
|
21
|
+
private RubyHash cover;
|
22
|
+
|
23
|
+
private CoverageHook() {
|
24
|
+
super();
|
25
|
+
}
|
26
|
+
|
27
|
+
public boolean isActive() {
|
28
|
+
return active;
|
29
|
+
}
|
30
|
+
|
31
|
+
public void setActive(boolean active) {
|
32
|
+
this.active = active;
|
33
|
+
}
|
34
|
+
|
35
|
+
public void eventHandler(ThreadContext context, String event, String file, int line, String name, IRubyObject type) {
|
36
|
+
//Line numbers are 1s based. Arrays are zero based. We need to compensate for that.
|
37
|
+
line -= 1;
|
38
|
+
|
39
|
+
// Make sure that we have SCRIPT_LINES__ and it's a hash
|
40
|
+
RubyHash scriptLines = getScriptLines(context.getRuntime());
|
41
|
+
if (scriptLines == null || !scriptLines.containsKey(file)) {
|
42
|
+
return;
|
43
|
+
}
|
44
|
+
|
45
|
+
// make sure the file's source lines are in SCRIPT_LINES__
|
46
|
+
cover = getCover(context.getRuntime());
|
47
|
+
RubyArray lines = (RubyArray) scriptLines.get(file);
|
48
|
+
if (lines == null || cover == null){
|
49
|
+
return;
|
50
|
+
}
|
51
|
+
|
52
|
+
// make sure file's counts are in COVER and set to zero
|
53
|
+
RubyArray counts = (RubyArray) cover.get(file);
|
54
|
+
if (counts == null) {
|
55
|
+
counts = context.getRuntime().newArray();
|
56
|
+
for (int i = 0; i < lines.size(); i++) {
|
57
|
+
counts.add(Long.valueOf(0));
|
58
|
+
}
|
59
|
+
cover.put(file, counts);
|
60
|
+
}
|
61
|
+
|
62
|
+
// in the case of code generation (one example is instance_eval for routes optimization)
|
63
|
+
// We could get here and see that we are not matched up with what we expect
|
64
|
+
if (counts.size() <= line ) {
|
65
|
+
for (int i=counts.size(); i<= line; i++) {
|
66
|
+
counts.add(Long.valueOf(0));
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
if (!context.isWithinTrace()) {
|
71
|
+
try {
|
72
|
+
context.setWithinTrace(true);
|
73
|
+
// update counts in COVER
|
74
|
+
Long count = (Long) counts.get(line);
|
75
|
+
if (count == null) {
|
76
|
+
count = Long.valueOf(0);
|
77
|
+
}
|
78
|
+
count = Long.valueOf(count.longValue() + 1);
|
79
|
+
counts.set(line , count);
|
80
|
+
}
|
81
|
+
finally{
|
82
|
+
context.setWithinTrace(false);
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
public boolean isInterestedInEvent(RubyEvent event) {
|
88
|
+
return event == RubyEvent.CALL || event == RubyEvent.LINE || event == RubyEvent.RETURN || event == RubyEvent.CLASS || event == RubyEvent.C_RETURN || event == RubyEvent.C_CALL;
|
89
|
+
}
|
90
|
+
|
91
|
+
/*
|
92
|
+
* Returns the COVER hash, setting up the COVER constant if necessary.
|
93
|
+
* @param runtime
|
94
|
+
* @return
|
95
|
+
*/
|
96
|
+
public RubyHash getCover(Ruby runtime) {
|
97
|
+
if (cover == null) {
|
98
|
+
cover = RubyHash.newHash(runtime);
|
99
|
+
}
|
100
|
+
|
101
|
+
return cover;
|
102
|
+
}
|
103
|
+
|
104
|
+
public RubyHash getScriptLines(Ruby runtime) {
|
105
|
+
IRubyObject scriptLines = runtime.getObject().getConstantAt("SCRIPT_LINES__");
|
106
|
+
if (scriptLines instanceof RubyHash) {
|
107
|
+
return (RubyHash) scriptLines;
|
108
|
+
} else {
|
109
|
+
return null;
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
public IRubyObject resetCoverage(Ruby runtime) {
|
114
|
+
getCover(runtime).clear();
|
115
|
+
return runtime.getNil();
|
116
|
+
}
|
117
|
+
}
|
@@ -0,0 +1,130 @@
|
|
1
|
+
import org.jruby.Ruby;
|
2
|
+
import org.jruby.RubyArray;
|
3
|
+
import org.jruby.RubyFixnum;
|
4
|
+
import org.jruby.RubyHash;
|
5
|
+
import org.jruby.RubyModule;
|
6
|
+
import org.jruby.RubyObjectAdapter;
|
7
|
+
import org.jruby.RubySymbol;
|
8
|
+
import org.jruby.exceptions.RaiseException;
|
9
|
+
import org.jruby.anno.JRubyMethod;
|
10
|
+
import org.jruby.runtime.ThreadContext;
|
11
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
12
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
13
|
+
import org.jruby.javasupport.JavaEmbedUtils;
|
14
|
+
import org.jruby.javasupport.JavaUtil;
|
15
|
+
|
16
|
+
public class RcovrtService implements BasicLibraryService {
|
17
|
+
|
18
|
+
private static RubyObjectAdapter rubyApi;
|
19
|
+
|
20
|
+
public boolean basicLoad(Ruby runtime) {
|
21
|
+
RubyModule rcov = runtime.getOrCreateModule("Rcov");
|
22
|
+
RubyModule rcov__ = runtime.defineModuleUnder("RCOV__", rcov);
|
23
|
+
IRubyObject sl = runtime.getObject().getConstantAt("SCRIPT_LINES__");
|
24
|
+
|
25
|
+
if (sl == null) {
|
26
|
+
runtime.getObject().setConstant("SCRIPT_LINES__", RubyHash.newHash(runtime));
|
27
|
+
}
|
28
|
+
|
29
|
+
rubyApi = JavaEmbedUtils.newObjectAdapter();
|
30
|
+
rcov__.defineAnnotatedMethods(RcovrtService.class);
|
31
|
+
return true;
|
32
|
+
}
|
33
|
+
|
34
|
+
@JRubyMethod(name="reset_callsite", meta = true)
|
35
|
+
public static IRubyObject resetCallsite(IRubyObject recv) {
|
36
|
+
CallsiteHook hook = CallsiteHook.getCallsiteHook();
|
37
|
+
if (hook.isActive()) {
|
38
|
+
throw RaiseException.createNativeRaiseException(recv.getRuntime(),
|
39
|
+
new RuntimeException("Cannot reset the callsite info in the middle of a traced run."));
|
40
|
+
}
|
41
|
+
return hook.resetDefsites();
|
42
|
+
}
|
43
|
+
|
44
|
+
@JRubyMethod(name="reset_coverage", meta = true)
|
45
|
+
public static IRubyObject resetCoverage(IRubyObject recv) {
|
46
|
+
CoverageHook hook = CoverageHook.getCoverageHook();
|
47
|
+
if (hook.isActive()) {
|
48
|
+
throw RaiseException.createNativeRaiseException(recv.getRuntime(),
|
49
|
+
new RuntimeException("Cannot reset the coverage info in the middle of a traced run."));
|
50
|
+
}
|
51
|
+
return hook.resetCoverage(recv.getRuntime());
|
52
|
+
}
|
53
|
+
|
54
|
+
@JRubyMethod(name="remove_coverage_hook", meta = true)
|
55
|
+
public static IRubyObject removeCoverageHook(IRubyObject recv) {
|
56
|
+
return removeRcovHook(recv, CoverageHook.getCoverageHook());
|
57
|
+
}
|
58
|
+
|
59
|
+
@JRubyMethod(name="install_coverage_hook", meta = true)
|
60
|
+
public static IRubyObject installCoverageHook(IRubyObject recv) {
|
61
|
+
return installRcovHook(recv, CoverageHook.getCoverageHook());
|
62
|
+
}
|
63
|
+
|
64
|
+
/*
|
65
|
+
TODO: I think this is broken. I'm not sure why, but recreating
|
66
|
+
cover all the time seems bad.
|
67
|
+
*/
|
68
|
+
@JRubyMethod(name="generate_coverage_info", meta = true)
|
69
|
+
public static IRubyObject generateCoverageInfo(IRubyObject recv) {
|
70
|
+
Ruby run = recv.getRuntime();
|
71
|
+
RubyHash cover = (RubyHash)CoverageHook.getCoverageHook().getCover(run);
|
72
|
+
RubyHash xcover = RubyHash.newHash(run);
|
73
|
+
RubyArray keys = cover.keys();
|
74
|
+
RubyArray temp;
|
75
|
+
ThreadContext ctx = run.getCurrentContext();
|
76
|
+
for (int i=0; i < keys.length().getLongValue(); i++) {
|
77
|
+
IRubyObject key = keys.aref(JavaUtil.convertJavaToRuby(run, Long.valueOf(i)));
|
78
|
+
temp = ((RubyArray)cover.op_aref(ctx, key)).aryDup();
|
79
|
+
xcover.op_aset(ctx,key, temp);
|
80
|
+
}
|
81
|
+
RubyModule rcov__ = (RubyModule) recv.getRuntime().getModule("Rcov").getConstant("RCOV__");
|
82
|
+
|
83
|
+
if (rcov__.const_defined_p(ctx, RubySymbol.newSymbol(recv.getRuntime(), "COVER")).isTrue()) {
|
84
|
+
rcov__.remove_const(ctx, recv.getRuntime().newString("COVER"));
|
85
|
+
}
|
86
|
+
rcov__.defineConstant( "COVER", xcover );
|
87
|
+
|
88
|
+
return xcover;
|
89
|
+
}
|
90
|
+
|
91
|
+
@JRubyMethod(name="remove_callsite_hook", meta = true)
|
92
|
+
public static IRubyObject removeCallsiteHook(IRubyObject recv) {
|
93
|
+
return removeRcovHook( recv, CallsiteHook.getCallsiteHook() );
|
94
|
+
}
|
95
|
+
|
96
|
+
@JRubyMethod(name="install_callsite_hook", meta = true)
|
97
|
+
public static IRubyObject installCallsiteHook(IRubyObject recv) {
|
98
|
+
return installRcovHook( recv, CallsiteHook.getCallsiteHook() );
|
99
|
+
}
|
100
|
+
|
101
|
+
@JRubyMethod(name="generate_callsite_info", meta = true)
|
102
|
+
public static IRubyObject generateCallsiteInfo(IRubyObject recv) {
|
103
|
+
return CallsiteHook.getCallsiteHook().getCallsiteInfo( recv.getRuntime() ).dup();
|
104
|
+
}
|
105
|
+
|
106
|
+
@JRubyMethod(name="ABI", meta = true)
|
107
|
+
public static IRubyObject getAbi(IRubyObject recv) {
|
108
|
+
RubyArray ary = recv.getRuntime().newArray();
|
109
|
+
ary.add(RubyFixnum.int2fix( recv.getRuntime(), 2L));
|
110
|
+
ary.add(RubyFixnum.int2fix( recv.getRuntime(), 0L));
|
111
|
+
ary.add(RubyFixnum.int2fix( recv.getRuntime(), 0L));
|
112
|
+
return ary;
|
113
|
+
}
|
114
|
+
|
115
|
+
private static IRubyObject removeRcovHook(IRubyObject recv, RcovHook hook) {
|
116
|
+
hook.setActive(false);
|
117
|
+
recv.getRuntime().removeEventHook(hook);
|
118
|
+
return recv.getRuntime().getFalse();
|
119
|
+
}
|
120
|
+
|
121
|
+
private static IRubyObject installRcovHook( IRubyObject recv, RcovHook hook ) {
|
122
|
+
if (!hook.isActive()) {
|
123
|
+
hook.setActive(true);
|
124
|
+
recv.getRuntime().addEventHook(hook);
|
125
|
+
return recv.getRuntime().getTrue();
|
126
|
+
} else {
|
127
|
+
return recv.getRuntime().getFalse();
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
data/lib/rcov.rb
ADDED
@@ -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
|