rcov 0.8.1.2.0 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/BLURB +2 -40
- data/LICENSE +2 -5
- data/Rakefile +32 -106
- data/THANKS +14 -0
- data/bin/rcov +277 -1090
- 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/{rcov.el → editor-extensions/rcov.el} +0 -0
- data/{rcov.vim → editor-extensions/rcov.vim} +0 -0
- data/ext/rcovrt/1.8/callsite.c +216 -0
- data/ext/rcovrt/1.8/rcovrt.c +287 -0
- data/ext/rcovrt/1.9/callsite.c +234 -0
- data/ext/rcovrt/1.9/rcovrt.c +264 -0
- data/ext/rcovrt/extconf.rb +12 -2
- data/lib/rcov.rb +13 -968
- 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 +16 -17
- data/lib/rcov/rcovtask.rb +21 -22
- 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 +5 -8
- data/setup.rb +5 -2
- data/test/{sample_01.rb → assets/sample_01.rb} +0 -0
- data/test/{sample_02.rb → assets/sample_02.rb} +0 -0
- data/test/{sample_03.rb → assets/sample_03.rb} +0 -0
- data/test/{sample_04.rb → assets/sample_04.rb} +0 -0
- data/test/{sample_05-new.rb → assets/sample_05-new.rb} +0 -0
- data/test/{sample_05-old.rb → assets/sample_05-old.rb} +0 -0
- data/test/{sample_05.rb → assets/sample_05.rb} +0 -0
- data/test/{test_CallSiteAnalyzer.rb → call_site_analyzer_test.rb} +57 -81
- data/test/{test_CodeCoverageAnalyzer.rb → code_coverage_analyzer_test.rb} +71 -35
- data/test/{test_FileStatistics.rb → file_statistics_test.rb} +34 -36
- data/test/{test_functional.rb → functional_test.rb} +21 -35
- metadata +91 -69
- data/CHANGES +0 -177
- data/LEGAL +0 -36
- data/README.API +0 -42
- data/README.emacs +0 -64
- data/README.en +0 -130
- data/README.rake +0 -62
- data/README.rant +0 -68
- data/README.vim +0 -47
- data/Rantfile +0 -76
- data/ext/rcovrt/callsite.c +0 -242
- data/ext/rcovrt/rcovrt.c +0 -329
- data/lib/rcov/rant.rb +0 -87
- data/lib/rcov/report.rb +0 -1236
- data/mingw-rbconfig.rb +0 -174
@@ -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
|
@@ -0,0 +1,268 @@
|
|
1
|
+
module Rcov
|
2
|
+
# A CodeCoverageAnalyzer is responsible for tracing code execution and
|
3
|
+
# returning code coverage and execution count information.
|
4
|
+
#
|
5
|
+
# Note that you must <tt>require 'rcov'</tt> before the code you want to
|
6
|
+
# analyze is parsed (i.e. before it gets loaded or required). You can do that
|
7
|
+
# by either invoking ruby with the <tt>-rrcov</tt> command-line option or
|
8
|
+
# just:
|
9
|
+
# require 'rcov'
|
10
|
+
# require 'mycode'
|
11
|
+
# # ....
|
12
|
+
#
|
13
|
+
# == Example
|
14
|
+
#
|
15
|
+
# analyzer = Rcov::CodeCoverageAnalyzer.new
|
16
|
+
# analyzer.run_hooked do
|
17
|
+
# do_foo
|
18
|
+
# # all the code executed as a result of this method call is traced
|
19
|
+
# end
|
20
|
+
# # ....
|
21
|
+
#
|
22
|
+
# analyzer.run_hooked do
|
23
|
+
# do_bar
|
24
|
+
# # the code coverage information generated in this run is aggregated
|
25
|
+
# # to the previously recorded one
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# analyzer.analyzed_files # => ["foo.rb", "bar.rb", ... ]
|
29
|
+
# lines, marked_info, count_info = analyzer.data("foo.rb")
|
30
|
+
#
|
31
|
+
# In this example, two pieces of code are monitored, and the data generated in
|
32
|
+
# both runs are aggregated. +lines+ is an array of strings representing the
|
33
|
+
# source code of <tt>foo.rb</tt>. +marked_info+ is an array holding false,
|
34
|
+
# true values indicating whether the corresponding lines of code were reported
|
35
|
+
# as executed by Ruby. +count_info+ is an array of integers representing how
|
36
|
+
# many times each line of code has been executed (more precisely, how many
|
37
|
+
# events where reported by Ruby --- a single line might correspond to several
|
38
|
+
# events, e.g. many method calls).
|
39
|
+
#
|
40
|
+
# You can have several CodeCoverageAnalyzer objects at a time, and it is
|
41
|
+
# possible to nest the #run_hooked / #install_hook/#remove_hook blocks: each
|
42
|
+
# analyzer will manage its data separately. Note however that no special
|
43
|
+
# provision is taken to ignore code executed "inside" the CodeCoverageAnalyzer
|
44
|
+
# class. At any rate this will not pose a problem since it's easy to ignore it
|
45
|
+
# manually: just don't do
|
46
|
+
# lines, coverage, counts = analyzer.data("/path/to/lib/rcov.rb")
|
47
|
+
# if you're not interested in that information.
|
48
|
+
class CodeCoverageAnalyzer < DifferentialAnalyzer
|
49
|
+
@hook_level = 0
|
50
|
+
# defined this way instead of attr_accessor so that it's covered
|
51
|
+
def self.hook_level # :nodoc:
|
52
|
+
@hook_level
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.hook_level=(x) # :nodoc:
|
56
|
+
@hook_level = x
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
@script_lines__ = SCRIPT_LINES__
|
61
|
+
super(:install_coverage_hook, :remove_coverage_hook,
|
62
|
+
:reset_coverage)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return an array with the names of the files whose code was executed inside
|
66
|
+
# the block given to #run_hooked or between #install_hook and #remove_hook.
|
67
|
+
def analyzed_files
|
68
|
+
update_script_lines__
|
69
|
+
raw_data_relative.select do |file, lines|
|
70
|
+
@script_lines__.has_key?(file)
|
71
|
+
end.map{|fname,| fname}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return the available data about the requested file, or nil if none of its
|
75
|
+
# code was executed or it cannot be found.
|
76
|
+
# The return value is an array with three elements:
|
77
|
+
# lines, marked_info, count_info = analyzer.data("foo.rb")
|
78
|
+
# +lines+ is an array of strings representing the
|
79
|
+
# source code of <tt>foo.rb</tt>. +marked_info+ is an array holding false,
|
80
|
+
# true values indicating whether the corresponding lines of code were reported
|
81
|
+
# as executed by Ruby. +count_info+ is an array of integers representing how
|
82
|
+
# many times each line of code has been executed (more precisely, how many
|
83
|
+
# events where reported by Ruby --- a single line might correspond to several
|
84
|
+
# events, e.g. many method calls).
|
85
|
+
#
|
86
|
+
# The returned data corresponds to the aggregation of all the statistics
|
87
|
+
# collected in each #run_hooked or #install_hook/#remove_hook runs. You can
|
88
|
+
# reset the data at any time with #reset to start from scratch.
|
89
|
+
def data(filename)
|
90
|
+
raw_data = raw_data_relative
|
91
|
+
update_script_lines__
|
92
|
+
unless @script_lines__.has_key?(filename) &&
|
93
|
+
raw_data.has_key?(filename)
|
94
|
+
return nil
|
95
|
+
end
|
96
|
+
refine_coverage_info(@script_lines__[filename], raw_data[filename])
|
97
|
+
end
|
98
|
+
|
99
|
+
# Data for the first file matching the given regexp.
|
100
|
+
# See #data.
|
101
|
+
def data_matching(filename_re)
|
102
|
+
raw_data = raw_data_relative
|
103
|
+
update_script_lines__
|
104
|
+
|
105
|
+
match = raw_data.keys.sort.grep(filename_re).first
|
106
|
+
return nil unless match
|
107
|
+
|
108
|
+
refine_coverage_info(@script_lines__[match], raw_data[match])
|
109
|
+
end
|
110
|
+
|
111
|
+
# Execute the code in the given block, monitoring it in order to gather
|
112
|
+
# information about which code was executed.
|
113
|
+
def run_hooked; super end
|
114
|
+
|
115
|
+
# Start monitoring execution to gather code coverage and execution count
|
116
|
+
# information. Such data will be collected until #remove_hook is called.
|
117
|
+
#
|
118
|
+
# Use #run_hooked instead if possible.
|
119
|
+
def install_hook; super end
|
120
|
+
|
121
|
+
# Stop collecting code coverage and execution count information.
|
122
|
+
# #remove_hook will also stop collecting info if it is run inside a
|
123
|
+
# #run_hooked block.
|
124
|
+
def remove_hook; super end
|
125
|
+
|
126
|
+
# Remove the data collected so far. The coverage and execution count
|
127
|
+
# "history" will be erased, and further collection will start from scratch:
|
128
|
+
# no code is considered executed, and therefore all execution counts are 0.
|
129
|
+
# Right after #reset, #analyzed_files will return an empty array, and
|
130
|
+
# #data(filename) will return nil.
|
131
|
+
def reset; super end
|
132
|
+
|
133
|
+
def dump_coverage_info(formatters) # :nodoc:
|
134
|
+
update_script_lines__
|
135
|
+
raw_data_relative.each do |file, lines|
|
136
|
+
next if @script_lines__.has_key?(file) == false
|
137
|
+
lines = @script_lines__[file]
|
138
|
+
raw_coverage_array = raw_data_relative[file]
|
139
|
+
|
140
|
+
line_info, marked_info,
|
141
|
+
count_info = refine_coverage_info(lines, raw_coverage_array)
|
142
|
+
formatters.each do |formatter|
|
143
|
+
formatter.add_file(file, line_info, marked_info, count_info)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
formatters.each{|formatter| formatter.execute}
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def data_default; {} end
|
152
|
+
|
153
|
+
def raw_data_absolute
|
154
|
+
Rcov::RCOV__.generate_coverage_info
|
155
|
+
end
|
156
|
+
|
157
|
+
def aggregate_data(aggregated_data, delta)
|
158
|
+
delta.each_pair do |file, cov_arr|
|
159
|
+
dest = (aggregated_data[file] ||= Array.new(cov_arr.size, 0))
|
160
|
+
cov_arr.each_with_index{|x,i| dest[i] += x.to_i}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def compute_raw_data_difference(first, last)
|
165
|
+
difference = {}
|
166
|
+
last.each_pair do |fname, cov_arr|
|
167
|
+
unless first.has_key?(fname)
|
168
|
+
difference[fname] = cov_arr.clone
|
169
|
+
else
|
170
|
+
orig_arr = first[fname]
|
171
|
+
diff_arr = Array.new(cov_arr.size, 0)
|
172
|
+
changed = false
|
173
|
+
cov_arr.each_with_index do |x, i|
|
174
|
+
diff_arr[i] = diff = (x || 0) - (orig_arr[i] || 0)
|
175
|
+
changed = true if diff != 0
|
176
|
+
end
|
177
|
+
difference[fname] = diff_arr if changed
|
178
|
+
end
|
179
|
+
end
|
180
|
+
difference
|
181
|
+
end
|
182
|
+
|
183
|
+
def refine_coverage_info(lines, covers)
|
184
|
+
marked_info = []
|
185
|
+
count_info = []
|
186
|
+
lines.size.times do |i|
|
187
|
+
c = covers[i]
|
188
|
+
marked_info << ((c && c > 0) ? true : false)
|
189
|
+
count_info << (c || 0)
|
190
|
+
end
|
191
|
+
|
192
|
+
script_lines_workaround(lines, marked_info, count_info)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Try to detect repeated data, based on observed repetitions in line_info:
|
196
|
+
# this is a workaround for SCRIPT_LINES__[filename] including as many copies
|
197
|
+
# of the file as the number of times it was parsed.
|
198
|
+
def script_lines_workaround(line_info, coverage_info, count_info)
|
199
|
+
is_repeated = lambda do |div|
|
200
|
+
n = line_info.size / div
|
201
|
+
break false unless line_info.size % div == 0 && n > 1
|
202
|
+
different = false
|
203
|
+
n.times do |i|
|
204
|
+
|
205
|
+
things = (0...div).map { |j| line_info[i + j * n] }
|
206
|
+
if things.uniq.size != 1
|
207
|
+
different = true
|
208
|
+
break
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
! different
|
213
|
+
end
|
214
|
+
|
215
|
+
factors = braindead_factorize(line_info.size)
|
216
|
+
factors.each do |n|
|
217
|
+
if is_repeated[n]
|
218
|
+
line_info = line_info[0, line_info.size / n]
|
219
|
+
coverage_info = coverage_info[0, coverage_info.size / n]
|
220
|
+
count_info = count_info[0, count_info.size / n]
|
221
|
+
end
|
222
|
+
end if factors.size > 1 # don't even try if it's prime
|
223
|
+
|
224
|
+
[line_info, coverage_info, count_info]
|
225
|
+
end
|
226
|
+
|
227
|
+
def braindead_factorize(num)
|
228
|
+
return [0] if num == 0
|
229
|
+
return [-1] + braindead_factorize(-num) if num < 0
|
230
|
+
factors = []
|
231
|
+
while num % 2 == 0
|
232
|
+
factors << 2
|
233
|
+
num /= 2
|
234
|
+
end
|
235
|
+
size = num
|
236
|
+
n = 3
|
237
|
+
max = Math.sqrt(num)
|
238
|
+
while n <= max && n <= size
|
239
|
+
while size % n == 0
|
240
|
+
size /= n
|
241
|
+
factors << n
|
242
|
+
end
|
243
|
+
n += 2
|
244
|
+
end
|
245
|
+
factors << size if size != 1
|
246
|
+
factors
|
247
|
+
end
|
248
|
+
|
249
|
+
def update_script_lines__
|
250
|
+
@script_lines__ = @script_lines__.merge(SCRIPT_LINES__)
|
251
|
+
end
|
252
|
+
|
253
|
+
public
|
254
|
+
|
255
|
+
def marshal_dump # :nodoc:
|
256
|
+
# @script_lines__ is updated just before serialization so as to avoid
|
257
|
+
# missing files in SCRIPT_LINES__
|
258
|
+
ivs = {}
|
259
|
+
update_script_lines__
|
260
|
+
instance_variables.each{|iv| ivs[iv] = instance_variable_get(iv)}
|
261
|
+
ivs
|
262
|
+
end
|
263
|
+
|
264
|
+
def marshal_load(ivs) # :nodoc:
|
265
|
+
ivs.each_pair{|iv, val| instance_variable_set(iv, val)}
|
266
|
+
end
|
267
|
+
end # CodeCoverageAnalyzer
|
268
|
+
end
|