relevance-rcov 0.9.3-java

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 (56) hide show
  1. data/BLURB +111 -0
  2. data/LICENSE +53 -0
  3. data/Rakefile +94 -0
  4. data/THANKS +110 -0
  5. data/bin/rcov +514 -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/java/src/CallsiteHook.java +137 -0
  13. data/ext/java/src/CoverageHook.java +117 -0
  14. data/ext/java/src/RcovHook.java +9 -0
  15. data/ext/java/src/RcovrtService.java +130 -0
  16. data/lib/rcov.rb +33 -0
  17. data/lib/rcov/call_site_analyzer.rb +225 -0
  18. data/lib/rcov/code_coverage_analyzer.rb +268 -0
  19. data/lib/rcov/coverage_info.rb +36 -0
  20. data/lib/rcov/differential_analyzer.rb +116 -0
  21. data/lib/rcov/file_statistics.rb +334 -0
  22. data/lib/rcov/formatters.rb +13 -0
  23. data/lib/rcov/formatters/base_formatter.rb +173 -0
  24. data/lib/rcov/formatters/failure_report.rb +15 -0
  25. data/lib/rcov/formatters/full_text_report.rb +48 -0
  26. data/lib/rcov/formatters/html_coverage.rb +274 -0
  27. data/lib/rcov/formatters/html_erb_template.rb +62 -0
  28. data/lib/rcov/formatters/text_coverage_diff.rb +193 -0
  29. data/lib/rcov/formatters/text_report.rb +32 -0
  30. data/lib/rcov/formatters/text_summary.rb +11 -0
  31. data/lib/rcov/lowlevel.rb +146 -0
  32. data/lib/rcov/rcovtask.rb +155 -0
  33. data/lib/rcov/templates/detail.html.erb +64 -0
  34. data/lib/rcov/templates/index.html.erb +93 -0
  35. data/lib/rcov/templates/jquery-1.3.2.min.js +19 -0
  36. data/lib/rcov/templates/jquery.tablesorter.min.js +15 -0
  37. data/lib/rcov/templates/print.css +12 -0
  38. data/lib/rcov/templates/rcov.js +42 -0
  39. data/lib/rcov/templates/screen.css +270 -0
  40. data/lib/rcov/version.rb +10 -0
  41. data/lib/rcovrt.jar +0 -0
  42. data/setup.rb +1588 -0
  43. data/test/assets/sample_01.rb +7 -0
  44. data/test/assets/sample_02.rb +5 -0
  45. data/test/assets/sample_03.rb +20 -0
  46. data/test/assets/sample_04.rb +10 -0
  47. data/test/assets/sample_05-new.rb +17 -0
  48. data/test/assets/sample_05-old.rb +13 -0
  49. data/test/assets/sample_05.rb +17 -0
  50. data/test/assets/sample_06.rb +8 -0
  51. data/test/call_site_analyzer_test.rb +171 -0
  52. data/test/code_coverage_analyzer_test.rb +219 -0
  53. data/test/file_statistics_test.rb +471 -0
  54. data/test/functional_test.rb +91 -0
  55. data/test/turn_off_rcovrt.rb +4 -0
  56. 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,9 @@
1
+ import org.jruby.runtime.EventHook;
2
+
3
+ public abstract class RcovHook extends EventHook {
4
+ /** returns true if the hook is set */
5
+ abstract boolean isActive();
6
+
7
+ /** used to mark the hook set or unset */
8
+ abstract void setActive(boolean active);
9
+ }
@@ -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