deep-cover 0.5.2 → 0.5.3
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.
- 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
|