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

test_background_runner_spec.rb « migrations « database « gitlab « lib « spec - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 9407efad91ff45ae764d97e189a37348d1aa7242 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do
  include Gitlab::Database::Migrations::BackgroundMigrationHelpers

  # In order to test the interaction between queueing sidekiq jobs and seeing those jobs in queues,
  # we need to disable sidekiq's testing mode and actually send our jobs to redis
  around do |ex|
    Sidekiq::Testing.disable! { ex.run }
  end

  let(:result_dir) { Dir.mktmpdir }

  after do
    FileUtils.rm_rf(result_dir)
  end

  context 'without jobs to run' do
    it 'returns immediately' do
      runner = described_class.new(result_dir: result_dir)
      expect(runner).not_to receive(:run_job)
      described_class.new(result_dir: result_dir).run_jobs(for_duration: 1.second)
    end
  end

  context 'with jobs to run' do
    let(:migration_name) { 'TestBackgroundMigration' }

    before do
      (1..5).each do |i|
        migrate_in(i.minutes, migration_name, [i])
      end
    end

    context 'finding pending background jobs' do
      it 'finds all the migrations' do
        expect(described_class.new(result_dir: result_dir).traditional_background_migrations.to_a.size).to eq(5)
      end
    end

    context 'running migrations', :freeze_time do
      def define_background_migration(name)
        klass = Class.new do
          # Can't simply def perform here as we won't have access to the block,
          # similarly can't define_method(:perform, &block) here as it would change the block receiver
          define_method(:perform) { |*args| yield(*args) }
        end
        stub_const("Gitlab::BackgroundMigration::#{name}", klass)
        klass
      end

      def expect_migration_call_counts(migrations_to_calls)
        migrations_to_calls.each do |migration, calls|
          expect_next_instances_of(migration, calls) do |m|
            expect(m).to receive(:perform).and_call_original
          end
        end
      end

      def expect_recorded_migration_runs(migrations_to_runs)
        migrations_to_runs.each do |migration, runs|
          path = File.join(result_dir, migration.name.demodulize)
          num_subdirs = Pathname(path).children.count(&:directory?)
          expect(num_subdirs).to eq(runs)
        end
      end

      def expect_migration_runs(migrations_to_run_counts)
        expect_migration_call_counts(migrations_to_run_counts)

        yield

        expect_recorded_migration_runs(migrations_to_run_counts)
      end

      it 'runs the migration class correctly' do
        calls = []
        define_background_migration(migration_name) do |i|
          calls << i
        end
        described_class.new(result_dir: result_dir).run_jobs(for_duration: 1.second) # Any time would work here as we do not advance time
        expect(calls).to contain_exactly(1, 2, 3, 4, 5)
      end

      it 'runs the migration for a uniform amount of time' do
        migration = define_background_migration(migration_name) do |i|
          travel(1.minute)
        end

        expect_migration_runs(migration => 3) do
          described_class.new(result_dir: result_dir).run_jobs(for_duration: 3.minutes)
        end
      end

      context 'with multiple migrations to run' do
        let(:other_migration_name) { 'OtherBackgroundMigration' }

        before do
          (1..5).each do |i|
            migrate_in(i.minutes, other_migration_name, [i])
          end
        end

        it 'splits the time between migrations when all migrations use all their time' do
          migration = define_background_migration(migration_name) do |i|
            travel(1.minute)
          end

          other_migration = define_background_migration(other_migration_name) do |i|
            travel(2.minutes)
          end

          expect_migration_runs(
            migration => 2, # 1 minute jobs for 90 seconds, can finish the first and start the second
            other_migration => 1 # 2 minute jobs for 90 seconds, past deadline after a single job
          ) do
            described_class.new(result_dir: result_dir).run_jobs(for_duration: 3.minutes)
          end
        end

        it 'does not give leftover time to extra migrations' do
          # This is currently implemented this way for simplicity, but it could make sense to change this behavior.

          migration = define_background_migration(migration_name) do
            travel(1.second)
          end
          other_migration = define_background_migration(other_migration_name) do
            travel(1.minute)
          end

          expect_migration_runs(
            migration => 5,
            other_migration => 2
          ) do
            described_class.new(result_dir: result_dir).run_jobs(for_duration: 3.minutes)
          end
        end
      end
    end
  end
end