deep-cover 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.deep_cover.rb +8 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +15 -1
- data/.travis.yml +1 -0
- data/README.md +30 -1
- data/Rakefile +10 -1
- data/bin/cov +1 -1
- data/deep_cover.gemspec +4 -5
- data/exe/deep-cover +5 -3
- data/lib/deep_cover.rb +1 -1
- data/lib/deep_cover/analyser/node.rb +1 -1
- data/lib/deep_cover/analyser/ruby25_like_branch.rb +209 -0
- data/lib/deep_cover/auto_run.rb +19 -19
- data/lib/deep_cover/autoload_tracker.rb +181 -44
- data/lib/deep_cover/backports.rb +3 -1
- data/lib/deep_cover/base.rb +13 -8
- data/lib/deep_cover/basics.rb +1 -1
- data/lib/deep_cover/cli/debugger.rb +2 -2
- data/lib/deep_cover/cli/instrumented_clone_reporter.rb +21 -8
- data/lib/deep_cover/cli/runner.rb +126 -0
- data/lib/deep_cover/config_setter.rb +1 -0
- data/lib/deep_cover/core_ext/autoload_overrides.rb +82 -14
- data/lib/deep_cover/core_ext/coverage_replacement.rb +34 -5
- data/lib/deep_cover/core_ext/exec_callbacks.rb +27 -0
- data/lib/deep_cover/core_ext/load_overrides.rb +4 -6
- data/lib/deep_cover/core_ext/require_overrides.rb +1 -3
- data/lib/deep_cover/coverage.rb +105 -2
- data/lib/deep_cover/coverage/analysis.rb +30 -28
- data/lib/deep_cover/coverage/persistence.rb +60 -70
- data/lib/deep_cover/covered_code.rb +16 -49
- data/lib/deep_cover/custom_requirer.rb +112 -51
- data/lib/deep_cover/load.rb +10 -6
- data/lib/deep_cover/memoize.rb +1 -3
- data/lib/deep_cover/module_override.rb +7 -0
- data/lib/deep_cover/node/assignments.rb +2 -1
- data/lib/deep_cover/node/base.rb +6 -6
- data/lib/deep_cover/node/block.rb +10 -8
- data/lib/deep_cover/node/case.rb +3 -3
- data/lib/deep_cover/node/collections.rb +8 -0
- data/lib/deep_cover/node/if.rb +19 -3
- data/lib/deep_cover/node/literals.rb +28 -7
- data/lib/deep_cover/node/mixin/can_augment_children.rb +4 -4
- data/lib/deep_cover/node/mixin/child_can_be_empty.rb +1 -1
- data/lib/deep_cover/node/mixin/filters.rb +6 -2
- data/lib/deep_cover/node/mixin/has_child.rb +8 -8
- data/lib/deep_cover/node/mixin/has_child_handler.rb +3 -3
- data/lib/deep_cover/node/mixin/has_tracker.rb +7 -3
- data/lib/deep_cover/node/root.rb +1 -1
- data/lib/deep_cover/node/send.rb +53 -7
- data/lib/deep_cover/node/short_circuit.rb +11 -3
- data/lib/deep_cover/parser_ext/range.rb +11 -27
- data/lib/deep_cover/problem_with_diagnostic.rb +1 -1
- data/lib/deep_cover/reporter.rb +0 -1
- data/lib/deep_cover/reporter/base.rb +68 -0
- data/lib/deep_cover/reporter/html.rb +1 -1
- data/lib/deep_cover/reporter/html/index.rb +4 -8
- data/lib/deep_cover/reporter/html/site.rb +10 -18
- data/lib/deep_cover/reporter/html/source.rb +3 -3
- data/lib/deep_cover/reporter/html/template/source.html.erb +1 -1
- data/lib/deep_cover/reporter/istanbul.rb +86 -56
- data/lib/deep_cover/reporter/text.rb +5 -13
- data/lib/deep_cover/reporter/{util/tree.rb → tree/util.rb} +19 -21
- data/lib/deep_cover/tools/blank.rb +25 -0
- data/lib/deep_cover/tools/builtin_coverage.rb +8 -8
- data/lib/deep_cover/tools/dump_covered_code.rb +2 -9
- data/lib/deep_cover/tools/execute_sample.rb +17 -6
- data/lib/deep_cover/tools/format_generated_code.rb +1 -1
- data/lib/deep_cover/tools/indent_string.rb +26 -0
- data/lib/deep_cover/tools/our_coverage.rb +2 -2
- data/lib/deep_cover/tools/strip_heredoc.rb +18 -0
- data/lib/deep_cover/tracker_bucket.rb +50 -0
- data/lib/deep_cover/tracker_hits_per_path.rb +35 -0
- data/lib/deep_cover/tracker_storage.rb +76 -0
- data/lib/deep_cover/tracker_storage_per_path.rb +34 -0
- data/lib/deep_cover/version.rb +1 -1
- data/lib/deep_cover_entry.rb +3 -0
- metadata +30 -37
- data/bin/gemcov +0 -8
- data/bin/selfcov +0 -21
- data/lib/deep_cover/cli/deep_cover.rb +0 -126
- data/lib/deep_cover/coverage/base.rb +0 -81
- data/lib/deep_cover/coverage/istanbul.rb +0 -34
- data/lib/deep_cover/tools/transform_keys.rb +0 -9
@@ -1,18 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DeepCover
|
4
|
-
|
5
|
-
class Text
|
6
|
-
include Util::Tree
|
7
|
-
def initialize(coverage, **options)
|
8
|
-
@coverage = coverage
|
9
|
-
@options = options
|
10
|
-
end
|
11
|
-
|
12
|
-
def analysis
|
13
|
-
Coverage::Analysis.new(@coverage.covered_codes, **@options)
|
14
|
-
end
|
4
|
+
require_relative 'base'
|
15
5
|
|
6
|
+
module Reporter
|
7
|
+
class Text < Base
|
16
8
|
INDENT = ' '
|
17
9
|
def report
|
18
10
|
formatted_headings = headings.map.with_index { |h, i| {value: h, alignment: :center} }
|
@@ -24,7 +16,7 @@ module DeepCover
|
|
24
16
|
style: {border_bottom: false, border_top: false, alignment: :right},
|
25
17
|
)
|
26
18
|
table.align_column 0, :left
|
27
|
-
table.render
|
19
|
+
table.render + "\n\nOverall: #{analysis.overall}%"
|
28
20
|
end
|
29
21
|
|
30
22
|
def self.report(coverage, **options)
|
@@ -43,7 +35,7 @@ module DeepCover
|
|
43
35
|
end
|
44
36
|
|
45
37
|
def rows
|
46
|
-
populate_stats
|
38
|
+
populate_stats.map do |full_path, partial_path, data, children|
|
47
39
|
[partial_path, *transform_data(data)]
|
48
40
|
end
|
49
41
|
end
|
@@ -2,9 +2,26 @@
|
|
2
2
|
|
3
3
|
module DeepCover
|
4
4
|
module Reporter
|
5
|
-
|
5
|
+
class Tree
|
6
6
|
# Utility functions to deal with trees
|
7
|
-
module
|
7
|
+
module Util
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def populate_from_map(tree:, map:, merge:)
|
11
|
+
return to_enum(__method__, tree: tree, map: map, merge: merge) unless block_given?
|
12
|
+
final_results, _final_data = populate(tree) do |full_path, partial_path, children|
|
13
|
+
if children.empty?
|
14
|
+
data = map.fetch(full_path)
|
15
|
+
else
|
16
|
+
child_results, child_data = children.transpose
|
17
|
+
data = merge.call(child_data)
|
18
|
+
end
|
19
|
+
result = yield full_path, partial_path, data, child_results || []
|
20
|
+
[result, data]
|
21
|
+
end.transpose
|
22
|
+
final_results
|
23
|
+
end
|
24
|
+
|
8
25
|
def paths_to_tree(paths)
|
9
26
|
twigs = paths.map do |path|
|
10
27
|
partials = path_to_partial_paths(path)
|
@@ -63,25 +80,6 @@ module DeepCover
|
|
63
80
|
yield full_path, path, children
|
64
81
|
end
|
65
82
|
end
|
66
|
-
|
67
|
-
# Same as populate, but also yields data, which is either the analysis data (for leaves)
|
68
|
-
# of the sum of the children (for subtrees)
|
69
|
-
def populate_stats(analysis)
|
70
|
-
return to_enum(__method__, analysis) unless block_given?
|
71
|
-
map = Tools.transform_keys(analysis.stat_map, &:name)
|
72
|
-
tree = paths_to_tree(map.keys)
|
73
|
-
final_results, _final_data = populate(tree) do |full_path, partial_path, children|
|
74
|
-
if children.empty?
|
75
|
-
data = map[full_path]
|
76
|
-
else
|
77
|
-
child_results, child_data = children.transpose
|
78
|
-
data = Tools.merge(*child_data, :+)
|
79
|
-
end
|
80
|
-
result = yield full_path, partial_path, data, child_results || []
|
81
|
-
[result, data]
|
82
|
-
end.transpose
|
83
|
-
final_results
|
84
|
-
end
|
85
83
|
end
|
86
84
|
end
|
87
85
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Tools::Blank
|
5
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
6
|
+
|
7
|
+
# Homemade poor-man's blank?
|
8
|
+
# Based, but modified, on https://github.com/rails/rails/blob/5-0-stable/activesupport/lib/active_support/core_ext/object/blank.rb
|
9
|
+
def blank?(obj)
|
10
|
+
if obj.is_a?(String)
|
11
|
+
obj.empty? || obj =~ BLANK_RE
|
12
|
+
else
|
13
|
+
obj.respond_to?(:empty?) ? !!obj.empty? : !obj
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def present?(obj)
|
18
|
+
!blank?(obj)
|
19
|
+
end
|
20
|
+
|
21
|
+
def presence(obj)
|
22
|
+
obj if present?(obj)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -2,22 +2,22 @@
|
|
2
2
|
|
3
3
|
module DeepCover
|
4
4
|
module Tools::BuiltinCoverage
|
5
|
-
def builtin_coverage(source,
|
5
|
+
def builtin_coverage(source, filename, lineno)
|
6
6
|
require 'coverage'
|
7
|
-
|
7
|
+
filename = File.absolute_path(File.expand_path(filename))
|
8
8
|
::Coverage.start
|
9
9
|
Tools.silence_warnings do
|
10
|
-
execute_sample -> { run_with_line_coverage(source,
|
10
|
+
execute_sample -> { run_with_line_coverage(source, filename, lineno) }
|
11
11
|
end
|
12
|
-
unshift_coverage(::Coverage.result.fetch(
|
12
|
+
unshift_coverage(::Coverage.result.fetch(filename), lineno)
|
13
13
|
end
|
14
14
|
|
15
15
|
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
16
16
|
# Executes the source as if it was in the specified file while
|
17
17
|
# builtin coverage information is still captured
|
18
|
-
def run_with_line_coverage(source,
|
18
|
+
def run_with_line_coverage(source, filename = nil, lineno = 1)
|
19
19
|
source = shift_source(source, lineno)
|
20
|
-
Object.to_java.getRuntime.executeScript(source,
|
20
|
+
Object.to_java.getRuntime.executeScript(source, filename)
|
21
21
|
end
|
22
22
|
else
|
23
23
|
# In ruby 2.0 and 2.1, using 2, 3 or 4 as lineno with RubyVM::InstructionSequence.compile
|
@@ -32,9 +32,9 @@ module DeepCover
|
|
32
32
|
|
33
33
|
# Executes the source as if it was in the specified file while
|
34
34
|
# builtin coverage information is still captured
|
35
|
-
def run_with_line_coverage(source,
|
35
|
+
def run_with_line_coverage(source, filename = nil, lineno = 1)
|
36
36
|
source = shift_source(source, lineno)
|
37
|
-
RubyVM::InstructionSequence.compile(source,
|
37
|
+
RubyVM::InstructionSequence.compile(source, filename).eval
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -5,22 +5,15 @@ module DeepCover
|
|
5
5
|
require 'with_progress'
|
6
6
|
end
|
7
7
|
module Tools::DumpCoveredCode
|
8
|
-
def
|
9
|
-
coverage = Coverage.new(tracker_global: '$_sc')
|
10
|
-
dump_covered_code(source_path, coverage: coverage, dest_path: dest_path)
|
11
|
-
coverage.save(dest_path)
|
12
|
-
end
|
13
|
-
|
14
|
-
def dump_covered_code(source_path, coverage:, dest_path: Dir.mktmpdir, root_path: source_path)
|
8
|
+
def dump_covered_code(source_path, coverage:, dest_path: Dir.mktmpdir)
|
15
9
|
source_path = File.join(File.expand_path(source_path), '')
|
16
10
|
dest_path = File.join(File.expand_path(dest_path), '')
|
17
|
-
root_path = Pathname.new(root_path)
|
18
11
|
skipped = []
|
19
12
|
file_paths = Dir.glob("#{source_path}**/*.rb").select { |p| File.file?(p) }
|
20
13
|
file_paths.each.with_progress(title: 'Rewriting') do |path|
|
21
14
|
new_path = Pathname(path.gsub(source_path, dest_path))
|
22
15
|
begin
|
23
|
-
covered_code = coverage.covered_code(path
|
16
|
+
covered_code = coverage.covered_code(path)
|
24
17
|
rescue Parser::SyntaxError
|
25
18
|
skipped << path
|
26
19
|
next
|
@@ -2,8 +2,11 @@
|
|
2
2
|
|
3
3
|
module DeepCover
|
4
4
|
module Tools::ExecuteSample
|
5
|
+
class ExceptionInSample < StandardError
|
6
|
+
end
|
7
|
+
|
5
8
|
# Returns true if the code would have continued, false if the rescue was triggered.
|
6
|
-
def execute_sample(to_execute)
|
9
|
+
def execute_sample(to_execute, source: nil)
|
7
10
|
# Disable some annoying warning by ruby. We are testing edge cases, so warnings are to be expected.
|
8
11
|
Tools.silence_warnings do
|
9
12
|
if to_execute.is_a?(CoveredCode)
|
@@ -13,11 +16,19 @@ module DeepCover
|
|
13
16
|
end
|
14
17
|
end
|
15
18
|
true
|
16
|
-
rescue
|
17
|
-
# In our samples, a simple `raise` doesn't need to be rescued
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
rescue StandardError => e
|
20
|
+
# In our samples, a simple `raise` is expected and doesn't need to be rescued
|
21
|
+
return false if e.is_a?(RuntimeError) && e.message.empty?
|
22
|
+
|
23
|
+
source = to_execute.covered_source if to_execute.is_a?(CoveredCode)
|
24
|
+
raise unless source
|
25
|
+
|
26
|
+
inner_msg = Tools.indent_string("#{e.class.name}: #{e.message}", 4)
|
27
|
+
source = Tools.indent_string(source, 4)
|
28
|
+
msg = "Exception when executing the sample:\n#{inner_msg}\n*Code follows*\n#{source}"
|
29
|
+
new_exc = ExceptionInSample.new(msg)
|
30
|
+
new_exc.set_backtrace(e.backtrace)
|
31
|
+
raise new_exc
|
21
32
|
end
|
22
33
|
end
|
23
34
|
end
|
@@ -15,7 +15,7 @@ module DeepCover
|
|
15
15
|
inserts.each do |exp_limit, size|
|
16
16
|
# Line index starts at 1, so array index returns the next line
|
17
17
|
comment_line = generated_lines[exp_limit.line]
|
18
|
-
next
|
18
|
+
next if Tools.blank?(comment_line)
|
19
19
|
next unless comment_line.start_with?('#>')
|
20
20
|
next if comment_line.start_with?('#>X')
|
21
21
|
next unless comment_line.size >= exp_limit.column
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Tools::IndentString
|
5
|
+
# In-place implementation copied from active-support.
|
6
|
+
IMPLEMENTATION = ->(amount, indent_string = nil, indent_empty_lines = false) do
|
7
|
+
indent_string = indent_string || self[/^[ \t]/] || ' '
|
8
|
+
re = indent_empty_lines ? /^/ : /^(?!$)/
|
9
|
+
gsub!(re, indent_string * amount)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Same as #indent! from active-support
|
13
|
+
# https://github.com/rails/rails/blob/10e1f1f9a129f2f197a44009a99b73b8ff9dbc0d/activesupport/lib/active_support/core_ext/string/indent.rb#L7
|
14
|
+
def indent_string!(string, *args)
|
15
|
+
string.instance_exec(*args, &IMPLEMENTATION)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Same as #indent from active-support
|
19
|
+
# https://github.com/rails/rails/blob/10e1f1f9a129f2f197a44009a99b73b8ff9dbc0d/activesupport/lib/active_support/core_ext/string/indent.rb#L42
|
20
|
+
def indent_string(string, *args)
|
21
|
+
string = string.dup
|
22
|
+
indent_string!(string, *args)
|
23
|
+
string
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
module DeepCover
|
4
4
|
module Tools::OurCoverage
|
5
|
-
def our_coverage(source,
|
6
|
-
covered_code = CoveredCode.new(source: source, path:
|
5
|
+
def our_coverage(source, filename, lineno, **options)
|
6
|
+
covered_code = CoveredCode.new(source: source, path: filename, lineno: lineno)
|
7
7
|
Tools.execute_sample(covered_code)
|
8
8
|
covered_code.line_coverage(options)[(lineno - 1)..-1]
|
9
9
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Tools::StripHeredoc
|
5
|
+
# In-place implementation copied from active-support.
|
6
|
+
IMPLEMENTATION = -> do
|
7
|
+
gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, ''.freeze).tap do |stripped|
|
8
|
+
stripped.freeze if frozen?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Same as #strip_heredoc from active-support
|
13
|
+
# https://github.com/rails/rails/blob/16574409f813e2197f88e4a06b527618d64d9ff0/activesupport/lib/active_support/core_ext/string/strip.rb#L22
|
14
|
+
def strip_heredoc(string)
|
15
|
+
string.instance_exec(&IMPLEMENTATION)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
bootstrap
|
5
|
+
|
6
|
+
require_relative 'tracker_storage'
|
7
|
+
|
8
|
+
# A holder for TrackerStorages, using some `global_name`.
|
9
|
+
class TrackerBucket
|
10
|
+
@@index = {}
|
11
|
+
|
12
|
+
def self.[](global_name)
|
13
|
+
raise ArgumentError, "'#{global_name}' is not a valid global name" unless global_name.start_with? '$'
|
14
|
+
@@index[global_name] ||= new(global_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup_source
|
18
|
+
"#{source} ||= {}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def source
|
22
|
+
@global_name
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
alias_method :_load, :[]
|
27
|
+
private :_load, :new
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect
|
31
|
+
%{#<DeepCover::TrackerBucket "#{@global_name}">}
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_storage(index = nil)
|
35
|
+
index ||= @global.size
|
36
|
+
TrackerStorage.new(bucket: self, array: @global[index] ||= [], index: index)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def initialize(global_name)
|
42
|
+
@global_name = global_name
|
43
|
+
@global = eval(setup_source) # rubocop:disable Security/Eval
|
44
|
+
end
|
45
|
+
|
46
|
+
def _dump(_level)
|
47
|
+
@global_name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
bootstrap
|
5
|
+
|
6
|
+
# Should be seen as a hash like {path => tracker_hits, ...},
|
7
|
+
# where tracker_hits is simply an array of integers returned from
|
8
|
+
# TrackerStorage#tracker_hits.
|
9
|
+
# Make it easier to separate some concerns, as well as marshalling.
|
10
|
+
#
|
11
|
+
class TrackerHitsPerPath
|
12
|
+
extend Forwardable
|
13
|
+
def_delegators :@index, :each, :each_key, :map, :transform_values, :to_h, :to_hash
|
14
|
+
|
15
|
+
def initialize(index = {})
|
16
|
+
@index = index
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](val)
|
20
|
+
@index[val] ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def merge!(tracker_hits_per_path)
|
24
|
+
@index.merge!(tracker_hits_per_path) { |_h, actual, to_merge| merge_tracker_hits(actual, to_merge) }
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
private def merge_tracker_hits(hits, to_merge)
|
29
|
+
unless hits.size == to_merge.size
|
30
|
+
raise "Attempting to merge trackers of different sizes: #{hits.size} vs #{to_merge.size}"
|
31
|
+
end
|
32
|
+
hits.map!.with_index { |val, i| val + to_merge[i] }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
bootstrap
|
5
|
+
|
6
|
+
# List of allocated trackers from a bucket.
|
7
|
+
# Should be thought of as a simple array of integers with
|
8
|
+
# a limited interface.
|
9
|
+
class TrackerBucket
|
10
|
+
class TrackerStorage
|
11
|
+
extend Forwardable
|
12
|
+
def_delegators :@array, :[], :size, :each, :map, :fetch
|
13
|
+
|
14
|
+
attr_reader :bucket
|
15
|
+
|
16
|
+
def initialize(bucket:, array:, index:)
|
17
|
+
@bucket = bucket
|
18
|
+
@array = array
|
19
|
+
@index = index
|
20
|
+
@allocated = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns a range of tracker ids
|
24
|
+
def allocate_trackers(nb_needed)
|
25
|
+
prev = @allocated
|
26
|
+
@allocated += nb_needed
|
27
|
+
missing = @allocated - @array.size
|
28
|
+
@array.concat(Array.new(missing, 0)) if missing > 0
|
29
|
+
prev...@allocated
|
30
|
+
end
|
31
|
+
|
32
|
+
def setup_source
|
33
|
+
"(#{bucket.setup_source})[#{@index}]||=Array.new(#{size},0)"
|
34
|
+
end
|
35
|
+
|
36
|
+
def tracker_source(tracker_id)
|
37
|
+
"#{bucket.source}[#{@index}][#{tracker_id}]+=1"
|
38
|
+
end
|
39
|
+
|
40
|
+
def tracker_hits
|
41
|
+
@array.dup.freeze
|
42
|
+
end
|
43
|
+
|
44
|
+
def tracker_hits=(new_hits)
|
45
|
+
if new_hits.size != @array.size
|
46
|
+
warn 'Replacing tracker hits with array of different size'
|
47
|
+
end
|
48
|
+
@array.replace(new_hits)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def dump
|
54
|
+
{bucket: @bucket, index: @index, size: @array.size}
|
55
|
+
end
|
56
|
+
|
57
|
+
def _dump(_level)
|
58
|
+
Marshal.dump(dump)
|
59
|
+
end
|
60
|
+
|
61
|
+
class << self
|
62
|
+
private def load(bucket:, index:, size:)
|
63
|
+
storage = bucket.create_storage(index)
|
64
|
+
storage.allocate_trackers(size - storage.size)
|
65
|
+
storage
|
66
|
+
end
|
67
|
+
|
68
|
+
private def _load(data)
|
69
|
+
load(Marshal.load(data)) # rubocop:disable Security/MarshalLoad
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private_constant :TrackerStorage
|
75
|
+
end
|
76
|
+
end
|