Skip to content

Commit 29e232c

Browse files
Narrow statistics cache invalidation
1 parent 8fc3bd7 commit 29e232c

4 files changed

Lines changed: 45 additions & 65 deletions

File tree

lib/covered/markdown_summary.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@ def initialize(threshold: 1.0)
2323
# @parameter coverage [Covered::Coverage] The coverage object below the threshold.
2424
# @returns [Covered::Statistics] Statistics for all coverage objects, including omitted ones.
2525
def each(wrapper)
26-
coverages = []
26+
statistics = Statistics.new
2727

2828
wrapper.each do |coverage|
29-
coverages << coverage
29+
statistics << coverage
3030

3131
if @threshold.nil? or coverage.ratio < @threshold
3232
yield coverage
3333
end
3434
end
3535

36-
return Statistics.new(Statistics::Aggregate.new(coverages))
36+
return statistics
3737
end
3838

3939
# Print any annotations for the given line.

lib/covered/statistics.rb

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ class Statistics
1919
# @parameter coverage [Covered::Coverage] The coverage object to summarize.
2020
# @returns [Covered::Statistics] Statistics containing the given coverage.
2121
def self.for(coverage)
22-
self.new(Aggregate.new([coverage]))
22+
self.new.tap do |statistics|
23+
statistics << coverage
24+
end
2325
end
2426

2527
# Immutable aggregate coverage statistics.
@@ -42,7 +44,6 @@ def initialize(coverages = [])
4244
end
4345

4446
paths.each_value(&:freeze)
45-
@paths = paths.freeze
4647

4748
@count = paths.size
4849
@executable_count = paths.sum{|_path, coverage| coverage.executable_count}
@@ -63,23 +64,6 @@ def initialize(coverages = [])
6364
# @returns [Integer] The executed line count.
6465
attr :executed_count
6566

66-
# @attribute [Hash(String, Covered::Coverage)] Coverage statistics indexed by path.
67-
attr :paths
68-
69-
# Add coverage to a new aggregate statistics object.
70-
# @parameter coverage [Covered::Coverage] The coverage object to add.
71-
# @returns [Covered::Statistics::Aggregate] The new aggregate statistics.
72-
def with(coverage)
73-
self.class.new(@paths.values + [coverage])
74-
end
75-
76-
# Get coverage for the given path.
77-
# @parameter path [String] The source path.
78-
# @returns [Covered::Coverage | Nil] The merged coverage for the path.
79-
def [](path)
80-
@paths[path]
81-
end
82-
8367
# A JSON-compatible representation of these aggregate statistics.
8468
# @returns [Hash] The aggregate count, line counts and percentage.
8569
def as_json
@@ -99,31 +83,25 @@ def to_json(options)
9983
end
10084
end
10185

102-
# Initialize coverage statistics.
103-
# @parameter aggregate [Covered::Statistics::Aggregate] The aggregate coverage statistics.
104-
def initialize(aggregate = Aggregate.new)
105-
@aggregate = aggregate
86+
# Initialize empty coverage statistics.
87+
def initialize
88+
@total = nil
89+
@paths = Hash.new
10690
end
10791

108-
# @attribute [Covered::Statistics::Aggregate] The aggregate coverage statistics.
109-
attr :aggregate
110-
11192
# The total aggregate statistics.
11293
# @returns [Covered::Statistics::Aggregate] The total aggregate statistics.
11394
def total
114-
@aggregate
95+
@total ||= Aggregate.new(@paths.values)
11596
end
11697

117-
# Coverage statistics indexed by path.
118-
# @returns [Hash(String, Covered::Coverage)] The coverage statistics indexed by path.
119-
def paths
120-
@aggregate.paths
121-
end
98+
# @attribute [Hash(String, Covered::Coverage)] Coverage statistics indexed by path.
99+
attr :paths
122100

123101
# The number of unique paths with coverage data.
124102
# @returns [Integer] The number of unique paths.
125103
def count
126-
@aggregate.count
104+
@paths.size
127105
end
128106

129107
# The total number of executable lines.
@@ -141,7 +119,15 @@ def executed_count
141119
# Add coverage to these statistics.
142120
# @parameter coverage [Covered::Coverage] The coverage object to add.
143121
def << coverage
144-
@aggregate = @aggregate.with(coverage)
122+
current = @paths[coverage.path]
123+
124+
unless current
125+
current = @paths[coverage.path] = coverage.empty
126+
end
127+
128+
current.merge!(coverage)
129+
130+
@total = nil
145131

146132
return self
147133
end
@@ -150,7 +136,7 @@ def << coverage
150136
# @parameter path [String] The source path.
151137
# @returns [Covered::Coverage | Nil] The merged coverage for the path.
152138
def [](path)
153-
@aggregate[path]
139+
@paths[path]
154140
end
155141

156142
# A JSON-compatible representation of these statistics.

lib/covered/summary.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ def terminal(output)
4545
# @parameter coverage [Covered::Coverage] The coverage object below the threshold.
4646
# @returns [Covered::Statistics] Statistics for all coverage objects, including omitted ones.
4747
def each(wrapper)
48-
coverages = []
48+
statistics = Statistics.new
4949

5050
wrapper.each do |coverage|
51-
coverages << coverage
51+
statistics << coverage
5252

5353
if @threshold.nil? or coverage.ratio < @threshold
5454
yield coverage
5555
end
5656
end
5757

58-
return Statistics.new(Statistics::Aggregate.new(coverages))
58+
return statistics
5959
end
6060

6161
# Print any annotations for the given line.

test/covered/statistics.rb

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ def before
7979
expect(statistics).to be(:complete?)
8080
end
8181
end
82+
83+
with "after reading total before adding coverage" do
84+
let(:partial_coverage) {Covered::Coverage.new(source, [nil, 1, 0])}
85+
let(:complete_coverage) {Covered::Coverage.new(source, [nil, 0, 1])}
86+
87+
def before
88+
statistics << partial_coverage
89+
statistics.total
90+
statistics << complete_coverage
91+
super
92+
end
93+
94+
it "invalidates cached totals" do
95+
expect(statistics.count).to be == 1
96+
expect(statistics.executable_count).to be == 2
97+
expect(statistics.executed_count).to be == 1
98+
end
99+
end
82100
end
83101

84102
describe Covered::Statistics::Aggregate do
@@ -96,38 +114,14 @@ def before
96114
expect(aggregate.executable_count).to be == 3
97115
expect(aggregate.executed_count).to be == 2
98116
end
99-
100-
it "indexes merged coverage by path" do
101-
expect(aggregate["foo.rb"].counts).to be == [nil, 2, 1]
102-
expect(aggregate["bar.rb"].counts).to be == [nil, 0]
103-
end
104117
end
105118

106119
with "an existing aggregate" do
107120
let(:coverage) {Covered::Coverage.new(source, [nil, 1])}
108-
let(:other_coverage) {Covered::Coverage.new(other_source, [nil, 0])}
109121
let(:aggregate) {subject.new([coverage])}
110122

111123
it "is immutable" do
112124
expect(aggregate).to be(:frozen?)
113-
expect(aggregate.paths).to be(:frozen?)
114-
expect(aggregate["foo.rb"]).to be(:frozen?)
115-
116-
expect do
117-
aggregate.paths["bar.rb"] = other_coverage
118-
end.to raise_exception(FrozenError)
119-
end
120-
121-
it "returns a new aggregate when adding coverage" do
122-
next_aggregate = aggregate.with(other_coverage)
123-
124-
expect(aggregate.count).to be == 1
125-
expect(aggregate.executable_count).to be == 1
126-
expect(aggregate.executed_count).to be == 1
127-
128-
expect(next_aggregate.count).to be == 2
129-
expect(next_aggregate.executable_count).to be == 2
130-
expect(next_aggregate.executed_count).to be == 1
131125
end
132126
end
133127
end

0 commit comments

Comments
 (0)