Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/database/consistency_checker_spec.rb')
-rw-r--r--spec/lib/gitlab/database/consistency_checker_spec.rb189
1 files changed, 189 insertions, 0 deletions
diff --git a/spec/lib/gitlab/database/consistency_checker_spec.rb b/spec/lib/gitlab/database/consistency_checker_spec.rb
new file mode 100644
index 00000000000..2ff79d20786
--- /dev/null
+++ b/spec/lib/gitlab/database/consistency_checker_spec.rb
@@ -0,0 +1,189 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::ConsistencyChecker do
+ let(:batch_size) { 10 }
+ let(:max_batches) { 4 }
+ let(:max_runtime) { described_class::MAX_RUNTIME }
+ let(:metrics_counter) { Gitlab::Metrics.registry.get(:consistency_checks) }
+
+ subject(:consistency_checker) do
+ described_class.new(
+ source_model: Namespace,
+ target_model: Ci::NamespaceMirror,
+ source_columns: %w[id traversal_ids],
+ target_columns: %w[namespace_id traversal_ids]
+ )
+ end
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", batch_size)
+ stub_const("#{described_class.name}::MAX_BATCHES", max_batches)
+ redis_shared_state_cleanup! # For Prometheus Counters
+ end
+
+ after do
+ Gitlab::Metrics.reset_registry!
+ end
+
+ describe '#over_time_limit?' do
+ before do
+ allow(consistency_checker).to receive(:start_time).and_return(0)
+ end
+
+ it 'returns true only if the running time has exceeded MAX_RUNTIME' do
+ allow(consistency_checker).to receive(:monotonic_time).and_return(0, max_runtime - 1, max_runtime + 1)
+ expect(consistency_checker.monotonic_time).to eq(0)
+ expect(consistency_checker.send(:over_time_limit?)).to eq(false)
+ expect(consistency_checker.send(:over_time_limit?)).to eq(true)
+ end
+ end
+
+ describe '#execute' do
+ context 'when empty tables' do
+ it 'returns an empty response' do
+ expected_result = { matches: 0, mismatches: 0, batches: 0, mismatches_details: [], next_start_id: nil }
+ expect(consistency_checker.execute(start_id: 1)).to eq(expected_result)
+ end
+ end
+
+ context 'when the tables contain matching items' do
+ before do
+ create_list(:namespace, 50) # This will also create Ci::NameSpaceMirror objects
+ end
+
+ it 'does not process more than MAX_BATCHES' do
+ max_batches = 3
+ stub_const("#{described_class.name}::MAX_BATCHES", max_batches)
+ result = consistency_checker.execute(start_id: Namespace.minimum(:id))
+ expect(result[:batches]).to eq(max_batches)
+ expect(result[:matches]).to eq(max_batches * batch_size)
+ end
+
+ it 'doesn not exceed the MAX_RUNTIME' do
+ allow(consistency_checker).to receive(:monotonic_time).and_return(0, max_runtime - 1, max_runtime + 1)
+ result = consistency_checker.execute(start_id: Namespace.minimum(:id))
+ expect(result[:batches]).to eq(1)
+ expect(result[:matches]).to eq(1 * batch_size)
+ end
+
+ it 'returns the correct number of matches and batches checked' do
+ expected_result = {
+ next_start_id: Namespace.minimum(:id) + described_class::MAX_BATCHES * described_class::BATCH_SIZE,
+ batches: max_batches,
+ matches: max_batches * batch_size,
+ mismatches: 0,
+ mismatches_details: []
+ }
+ expect(consistency_checker.execute(start_id: Namespace.minimum(:id))).to eq(expected_result)
+ end
+
+ it 'returns the min_id as the next_start_id if the check reaches the last element' do
+ expect(Gitlab::Metrics).to receive(:counter).at_most(:once)
+ .with(:consistency_checks, "Consistency Check Results")
+ .and_call_original
+
+ # Starting from the 5th last element
+ start_id = Namespace.all.order(id: :desc).limit(5).pluck(:id).last
+ expected_result = {
+ next_start_id: Namespace.first.id,
+ batches: 1,
+ matches: 5,
+ mismatches: 0,
+ mismatches_details: []
+ }
+ expect(consistency_checker.execute(start_id: start_id)).to eq(expected_result)
+
+ expect(metrics_counter.get(source_table: "namespaces", result: "mismatch")).to eq(0)
+ expect(metrics_counter.get(source_table: "namespaces", result: "match")).to eq(5)
+ end
+ end
+
+ context 'when some items are missing from the first table' do
+ let(:missing_namespace) { Namespace.all.order(:id).limit(2).last }
+
+ before do
+ create_list(:namespace, 50) # This will also create Ci::NameSpaceMirror objects
+ missing_namespace.delete
+ end
+
+ it 'reports the missing elements' do
+ expected_result = {
+ next_start_id: Namespace.first.id + described_class::MAX_BATCHES * described_class::BATCH_SIZE,
+ batches: max_batches,
+ matches: 39,
+ mismatches: 1,
+ mismatches_details: [{
+ id: missing_namespace.id,
+ source_table: nil,
+ target_table: [missing_namespace.traversal_ids]
+ }]
+ }
+ expect(consistency_checker.execute(start_id: Namespace.first.id)).to eq(expected_result)
+
+ expect(metrics_counter.get(source_table: "namespaces", result: "mismatch")).to eq(1)
+ expect(metrics_counter.get(source_table: "namespaces", result: "match")).to eq(39)
+ end
+ end
+
+ context 'when some items are missing from the second table' do
+ let(:missing_ci_namespace_mirror) { Ci::NamespaceMirror.all.order(:id).limit(2).last }
+
+ before do
+ create_list(:namespace, 50) # This will also create Ci::NameSpaceMirror objects
+ missing_ci_namespace_mirror.delete
+ end
+
+ it 'reports the missing elements' do
+ expected_result = {
+ next_start_id: Namespace.first.id + described_class::MAX_BATCHES * described_class::BATCH_SIZE,
+ batches: 4,
+ matches: 39,
+ mismatches: 1,
+ mismatches_details: [{
+ id: missing_ci_namespace_mirror.namespace_id,
+ source_table: [missing_ci_namespace_mirror.traversal_ids],
+ target_table: nil
+ }]
+ }
+ expect(consistency_checker.execute(start_id: Namespace.first.id)).to eq(expected_result)
+
+ expect(metrics_counter.get(source_table: "namespaces", result: "mismatch")).to eq(1)
+ expect(metrics_counter.get(source_table: "namespaces", result: "match")).to eq(39)
+ end
+ end
+
+ context 'when elements are different between the two tables' do
+ let(:different_namespaces) { Namespace.order(:id).limit(max_batches * batch_size).sample(3).sort_by(&:id) }
+
+ before do
+ create_list(:namespace, 50) # This will also create Ci::NameSpaceMirror objects
+
+ different_namespaces.each do |namespace|
+ namespace.update_attribute(:traversal_ids, [])
+ end
+ end
+
+ it 'reports the difference between the two tables' do
+ expected_result = {
+ next_start_id: Namespace.first.id + described_class::MAX_BATCHES * described_class::BATCH_SIZE,
+ batches: 4,
+ matches: 37,
+ mismatches: 3,
+ mismatches_details: different_namespaces.map do |namespace|
+ {
+ id: namespace.id,
+ source_table: [[]],
+ target_table: [[namespace.id]] # old traversal_ids of the namespace
+ }
+ end
+ }
+ expect(consistency_checker.execute(start_id: Namespace.first.id)).to eq(expected_result)
+
+ expect(metrics_counter.get(source_table: "namespaces", result: "mismatch")).to eq(3)
+ expect(metrics_counter.get(source_table: "namespaces", result: "match")).to eq(37)
+ end
+ end
+ end
+end