BigW Consortium Gitlab

gitaly_client_spec.rb 9.76 KB
Newer Older
1 2
require 'spec_helper'

3 4
# We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want
# those stubs while testing the GitalyClient itself.
5
describe Gitlab::GitalyClient, skip_gitaly_mock: true do
6
  describe '.stub' do
7
    # Notice that this is referring to gRPC "stubs", not rspec stubs
8 9 10
    before do
      described_class.clear_stubs!
    end
11

12
    context 'when passed a UNIX socket address' do
13
      it 'passes the address as-is to GRPC' do
14
        address = 'unix:/tmp/gitaly.sock'
15 16 17
        allow(Gitlab.config.repositories).to receive(:storages).and_return({
          'default' => { 'gitaly_address' => address }
        })
18

19
        expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
20

21
        described_class.stub(:commit_service, 'default')
22 23 24 25 26 27 28 29
      end
    end

    context 'when passed a TCP address' do
      it 'strips tcp:// prefix before passing it to GRPC::Core::Channel initializer' do
        address = 'localhost:9876'
        prefixed_address = "tcp://#{address}"

30 31 32 33
        allow(Gitlab.config.repositories).to receive(:storages).and_return({
          'default' => { 'gitaly_address' => prefixed_address }
        })

34
        expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
35

36
        described_class.stub(:commit_service, 'default')
37 38 39
      end
    end
  end
40

41 42 43 44 45 46 47 48 49 50 51 52 53 54
  describe 'encode' do
    [
      [nil, ""],
      ["", ""],
      ["  ", "  "],
      %w(a1 a1),
      ["编码", "\xE7\xBC\x96\xE7\xA0\x81".b]
    ].each do |input, result|
      it "encodes #{input.inspect} to #{result.inspect}" do
        expect(described_class.encode(input)).to eq result
      end
    end
  end

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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
  describe 'allow_n_plus_1_calls' do
    context 'when RequestStore is enabled', :request_store do
      it 'returns the result of the allow_n_plus_1_calls block' do
        expect(described_class.allow_n_plus_1_calls { "result" }).to eq("result")
      end
    end

    context 'when RequestStore is not active' do
      it 'returns the result of the allow_n_plus_1_calls block' do
        expect(described_class.allow_n_plus_1_calls { "something" }).to eq("something")
      end
    end
  end

  describe 'enforce_gitaly_request_limits?' do
    def call_gitaly(count = 1)
      (1..count).each do
        described_class.enforce_gitaly_request_limits(:test)
      end
    end

    context 'when RequestStore is enabled', :request_store do
      it 'allows up the maximum number of allowed calls' do
        expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS) }.not_to raise_error
      end

      context 'when the maximum number of calls has been reached' do
        before do
          call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS)
        end

        it 'fails on the next call' do
          expect { call_gitaly(1) }.to raise_error(Gitlab::GitalyClient::TooManyInvocationsError)
        end
      end

      it 'allows the maximum number of calls to be exceeded within an allow_n_plus_1_calls block' do
        expect do
          described_class.allow_n_plus_1_calls do
            call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1)
          end
        end.not_to raise_error
      end

      context 'when the maximum number of calls has been reached within an allow_n_plus_1_calls block' do
        before do
          described_class.allow_n_plus_1_calls do
            call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS)
          end
        end

        it 'allows up to the maximum number of calls outside of an allow_n_plus_1_calls block' do
          expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS) }.not_to raise_error
        end

        it 'does not allow the maximum number of calls to be exceeded outside of an allow_n_plus_1_calls block' do
          expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.to raise_error(Gitlab::GitalyClient::TooManyInvocationsError)
        end
      end
    end

    context 'when RequestStore is not active' do
      it 'does not raise errors when the maximum number of allowed calls is exceeded' do
        expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 2) }.not_to raise_error
      end

      it 'does not fail when the maximum number of calls is exceeded within an allow_n_plus_1_calls block' do
        expect do
          described_class.allow_n_plus_1_calls do
            call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1)
          end
        end.not_to raise_error
      end
    end
  end

  describe 'get_request_count' do
    context 'when RequestStore is enabled', :request_store do
      context 'when enforce_gitaly_request_limits is called outside of allow_n_plus_1_calls blocks' do
        before do
          described_class.enforce_gitaly_request_limits(:call)
        end

        it 'counts gitaly calls' do
          expect(described_class.get_request_count).to eq(1)
        end
      end

      context 'when enforce_gitaly_request_limits is called inside and outside of allow_n_plus_1_calls blocks' do
        before do
          described_class.enforce_gitaly_request_limits(:call)
          described_class.allow_n_plus_1_calls do
            described_class.enforce_gitaly_request_limits(:call)
          end
        end

        it 'counts gitaly calls' do
          expect(described_class.get_request_count).to eq(2)
        end
      end

      context 'when reset_counts is called' do
        before do
          described_class.enforce_gitaly_request_limits(:call)
          described_class.reset_counts
        end

        it 'resets counts' do
          expect(described_class.get_request_count).to eq(0)
        end
      end
    end

    context 'when RequestStore is not active' do
      before do
        described_class.enforce_gitaly_request_limits(:call)
      end

      it 'returns zero' do
        expect(described_class.get_request_count).to eq(0)
      end
    end
  end

179 180 181 182 183
  describe 'feature_enabled?' do
    let(:feature_name) { 'my_feature' }
    let(:real_feature_name) { "gitaly_#{feature_name}" }

    context 'when Gitaly is disabled' do
184 185 186
      before do
        allow(described_class).to receive(:enabled?).and_return(false)
      end
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

      it 'returns false' do
        expect(described_class.feature_enabled?(feature_name)).to be(false)
      end
    end

    context 'when the feature status is DISABLED' do
      let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::DISABLED }

      it 'returns false' do
        expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
      end
    end

    context 'when the feature_status is OPT_IN' do
      let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::OPT_IN }

      context "when the feature flag hasn't been set" do
        it 'returns false' do
          expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
        end
      end

      context "when the feature flag is set to disable" do
211 212 213
        before do
          Feature.get(real_feature_name).disable
        end
214 215 216 217 218 219 220

        it 'returns false' do
          expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
        end
      end

      context "when the feature flag is set to enable" do
221 222 223
        before do
          Feature.get(real_feature_name).enable
        end
224 225 226 227 228 229 230

        it 'returns true' do
          expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
        end
      end

      context "when the feature flag is set to a percentage of time" do
231 232 233
        before do
          Feature.get(real_feature_name).enable_percentage_of_time(70)
        end
234 235 236 237 238 239 240 241 242

        it 'bases the result on pseudo-random numbers' do
          expect(Random).to receive(:rand).and_return(0.3)
          expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)

          expect(Random).to receive(:rand).and_return(0.8)
          expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
        end
      end
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258

      context "when a feature is not persisted" do
        it 'returns false when opt_into_all_features is off' do
          allow(Feature).to receive(:persisted?).and_return(false)
          allow(described_class).to receive(:opt_into_all_features?).and_return(false)

          expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
        end

        it 'returns true when the override is on' do
          allow(Feature).to receive(:persisted?).and_return(false)
          allow(described_class).to receive(:opt_into_all_features?).and_return(true)

          expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
        end
      end
259 260 261 262 263 264 265 266 267 268 269 270
    end

    context 'when the feature_status is OPT_OUT' do
      let(:feature_status) { Gitlab::GitalyClient::MigrationStatus::OPT_OUT }

      context "when the feature flag hasn't been set" do
        it 'returns true' do
          expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(true)
        end
      end

      context "when the feature flag is set to disable" do
271 272 273
        before do
          Feature.get(real_feature_name).disable
        end
274 275 276 277 278 279 280

        it 'returns false' do
          expect(described_class.feature_enabled?(feature_name, status: feature_status)).to be(false)
        end
      end
    end
  end
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

  describe 'timeouts' do
    context 'with default values' do
      before do
        stub_application_setting(gitaly_timeout_default: 55)
        stub_application_setting(gitaly_timeout_medium: 30)
        stub_application_setting(gitaly_timeout_fast: 10)
      end

      it 'returns expected values' do
        expect(described_class.default_timeout).to be(55)
        expect(described_class.medium_timeout).to be(30)
        expect(described_class.fast_timeout).to be(10)
      end
    end
  end
297
end