relevance-rcov 0.9.3-java

Sign up to get free protection for your applications and to get access to all the features.
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