summaryrefslogtreecommitdiff
path: root/spec/models/project_statistics_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/project_statistics_spec.rb')
-rw-r--r--spec/models/project_statistics_spec.rb168
1 files changed, 154 insertions, 14 deletions
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index a6e2bcf1525..ef53de6ad82 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -455,35 +455,50 @@ RSpec.describe ProjectStatistics do
end
describe '.increment_statistic' do
- shared_examples 'a statistic that increases storage_size' do
+ shared_examples 'a statistic that increases storage_size synchronously' do
+ let(:increment) { Gitlab::Counters::Increment.new(amount: 13) }
+
it 'increases the statistic by that amount' do
- expect { described_class.increment_statistic(project, stat, 13) }
+ expect { described_class.increment_statistic(project, stat, increment) }
.to change { statistics.reload.send(stat) || 0 }
- .by(13)
+ .by(increment.amount)
end
it 'increases also storage size by that amount' do
- expect { described_class.increment_statistic(project, stat, 20) }
+ expect { described_class.increment_statistic(project, stat, increment) }
.to change { statistics.reload.storage_size }
- .by(20)
+ .by(increment.amount)
end
it 'schedules a namespace aggregation worker' do
expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async)
.with(statistics.project.namespace.id)
- described_class.increment_statistic(project, stat, 20)
+ described_class.increment_statistic(project, stat, increment)
+ end
+
+ context 'when the project is pending delete' do
+ before do
+ project.update_attribute(:pending_delete, true)
+ end
+
+ it 'does not change the statistics' do
+ expect { described_class.increment_statistic(project, stat, increment) }
+ .not_to change { statistics.reload.send(stat) }
+ end
end
end
shared_examples 'a statistic that increases storage_size asynchronously' do
+ let(:increment) { Gitlab::Counters::Increment.new(amount: 13) }
+
it 'stores the increment temporarily in Redis', :clean_gitlab_redis_shared_state do
- described_class.increment_statistic(project, stat, 13)
+ described_class.increment_statistic(project, stat, increment)
Gitlab::Redis::SharedState.with do |redis|
key = statistics.counter(stat).key
- increment = redis.get(key)
- expect(increment.to_i).to eq(13)
+ value = redis.get(key)
+ expect(value.to_i).to eq(increment.amount)
end
end
@@ -493,9 +508,20 @@ RSpec.describe ProjectStatistics do
.with(Gitlab::Counters::BufferedCounter::WORKER_DELAY, described_class.name, statistics.id, stat)
.and_call_original
- expect { described_class.increment_statistic(project, stat, 20) }
- .to change { statistics.reload.send(stat) }.by(20)
- .and change { statistics.reload.send(:storage_size) }.by(20)
+ expect { described_class.increment_statistic(project, stat, increment) }
+ .to change { statistics.reload.send(stat) }.by(increment.amount)
+ .and change { statistics.reload.send(:storage_size) }.by(increment.amount)
+ end
+
+ context 'when the project is pending delete' do
+ before do
+ project.update_attribute(:pending_delete, true)
+ end
+
+ it 'does not change the statistics' do
+ expect { described_class.increment_statistic(project, stat, increment) }
+ .not_to change { [statistics.reload.send(stat), statistics.reload.send(:storage_size)] }
+ end
end
end
@@ -508,7 +534,7 @@ RSpec.describe ProjectStatistics do
context 'when adjusting :pipeline_artifacts_size' do
let(:stat) { :pipeline_artifacts_size }
- it_behaves_like 'a statistic that increases storage_size'
+ it_behaves_like 'a statistic that increases storage_size synchronously'
end
context 'when adjusting :packages_size' do
@@ -518,9 +544,11 @@ RSpec.describe ProjectStatistics do
end
context 'when the amount is 0' do
+ let(:increment) { Gitlab::Counters::Increment.new(amount: 0) }
+
it 'does not execute a query' do
project
- expect { described_class.increment_statistic(project, :build_artifacts_size, 0) }
+ expect { described_class.increment_statistic(project, :build_artifacts_size, increment) }
.not_to exceed_query_limit(0)
end
end
@@ -532,4 +560,116 @@ RSpec.describe ProjectStatistics do
end
end
end
+
+ describe '.bulk_increment_statistic' do
+ let(:increments) { [10, 3].map { |amount| Gitlab::Counters::Increment.new(amount: amount) } }
+ let(:total_amount) { increments.sum(&:amount) }
+
+ shared_examples 'a statistic that increases storage_size synchronously' do
+ it 'increases the statistic by that amount' do
+ expect { described_class.bulk_increment_statistic(project, stat, increments) }
+ .to change { statistics.reload.send(stat) || 0 }
+ .by(total_amount)
+ end
+
+ it 'increases also storage size by that amount' do
+ expect { described_class.bulk_increment_statistic(project, stat, increments) }
+ .to change { statistics.reload.storage_size }
+ .by(total_amount)
+ end
+
+ it 'schedules a namespace aggregation worker' do
+ expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async)
+ .with(statistics.project.namespace.id)
+
+ described_class.bulk_increment_statistic(project, stat, increments)
+ end
+
+ context 'when the project is pending delete' do
+ before do
+ project.update_attribute(:pending_delete, true)
+ end
+
+ it 'does not change the statistics' do
+ expect { described_class.bulk_increment_statistic(project, stat, increments) }
+ .not_to change { statistics.reload.send(stat) }
+ end
+ end
+ end
+
+ shared_examples 'a statistic that increases storage_size asynchronously' do
+ it 'stores the increment temporarily in Redis', :clean_gitlab_redis_shared_state do
+ described_class.bulk_increment_statistic(project, stat, increments)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ key = statistics.counter(stat).key
+ increment = redis.get(key)
+ expect(increment.to_i).to eq(total_amount)
+ end
+ end
+
+ it 'schedules a worker to update the statistic and storage_size async', :sidekiq_inline do
+ expect(FlushCounterIncrementsWorker)
+ .to receive(:perform_in)
+ .with(Gitlab::Counters::BufferedCounter::WORKER_DELAY, described_class.name, statistics.id, stat)
+ .and_call_original
+
+ expect { described_class.bulk_increment_statistic(project, stat, increments) }
+ .to change { statistics.reload.send(stat) }.by(total_amount)
+ .and change { statistics.reload.send(:storage_size) }.by(total_amount)
+ end
+
+ context 'when the project is pending delete' do
+ before do
+ project.update_attribute(:pending_delete, true)
+ end
+
+ it 'does not change the statistics' do
+ expect { described_class.bulk_increment_statistic(project, stat, increments) }
+ .not_to change { [statistics.reload.send(stat), statistics.reload.send(:storage_size)] }
+ end
+ end
+ end
+
+ context 'when adjusting :build_artifacts_size' do
+ let(:stat) { :build_artifacts_size }
+
+ it_behaves_like 'a statistic that increases storage_size asynchronously'
+
+ context 'when :project_statistics_bulk_increment flag is disabled' do
+ before do
+ stub_feature_flags(project_statistics_bulk_increment: false)
+ end
+
+ it 'calls increment_statistic on once with the sum of the increments' do
+ total_amount = increments.sum(&:amount)
+ expect(statistics)
+ .to receive(:increment_statistic).with(stat, have_attributes(amount: total_amount)).and_call_original
+
+ described_class.bulk_increment_statistic(project, stat, increments)
+ end
+
+ it_behaves_like 'a statistic that increases storage_size asynchronously'
+ end
+ end
+
+ context 'when adjusting :pipeline_artifacts_size' do
+ let(:stat) { :pipeline_artifacts_size }
+
+ it_behaves_like 'a statistic that increases storage_size synchronously'
+ end
+
+ context 'when adjusting :packages_size' do
+ let(:stat) { :packages_size }
+
+ it_behaves_like 'a statistic that increases storage_size asynchronously'
+ end
+
+ context 'when using an invalid column' do
+ it 'raises an error' do
+ expect { described_class.bulk_increment_statistic(project, :id, increments) }
+ .to raise_error(ArgumentError, "Cannot increment attribute: id")
+ end
+ end
+ end
end