diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-18 13:50:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-18 13:50:51 +0300 |
commit | db384e6b19af03b4c3c82a5760d83a3fd79f7982 (patch) | |
tree | 34beaef37df5f47ccbcf5729d7583aae093cffa0 /gems/gitlab-safe_request_store | |
parent | 54fd7b1bad233e3944434da91d257fa7f63c3996 (diff) |
Add latest changes from gitlab-org/gitlab@16-3-stable-eev16.3.0-rc42
Diffstat (limited to 'gems/gitlab-safe_request_store')
14 files changed, 701 insertions, 0 deletions
diff --git a/gems/gitlab-safe_request_store/.gitignore b/gems/gitlab-safe_request_store/.gitignore new file mode 100644 index 00000000000..b04a8c840df --- /dev/null +++ b/gems/gitlab-safe_request_store/.gitignore @@ -0,0 +1,11 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +# rspec failure tracking +.rspec_status diff --git a/gems/gitlab-safe_request_store/.gitlab-ci.yml b/gems/gitlab-safe_request_store/.gitlab-ci.yml new file mode 100644 index 00000000000..faf215536e2 --- /dev/null +++ b/gems/gitlab-safe_request_store/.gitlab-ci.yml @@ -0,0 +1,4 @@ +include: + - local: gems/gem.gitlab-ci.yml + inputs: + gem_name: "gitlab-safe_request_store"
\ No newline at end of file diff --git a/gems/gitlab-safe_request_store/.rspec b/gems/gitlab-safe_request_store/.rspec new file mode 100644 index 00000000000..34c5164d9b5 --- /dev/null +++ b/gems/gitlab-safe_request_store/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/gems/gitlab-safe_request_store/.rubocop.yml b/gems/gitlab-safe_request_store/.rubocop.yml new file mode 100644 index 00000000000..8c670b439d3 --- /dev/null +++ b/gems/gitlab-safe_request_store/.rubocop.yml @@ -0,0 +1,2 @@ +inherit_from: + - ../config/rubocop.yml diff --git a/gems/gitlab-safe_request_store/Gemfile b/gems/gitlab-safe_request_store/Gemfile new file mode 100644 index 00000000000..667a61d518e --- /dev/null +++ b/gems/gitlab-safe_request_store/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +# Specify your gem's dependencies in gitlab-safe_request_store.gemspec +gemspec diff --git a/gems/gitlab-safe_request_store/Gemfile.lock b/gems/gitlab-safe_request_store/Gemfile.lock new file mode 100644 index 00000000000..50ff694d7d5 --- /dev/null +++ b/gems/gitlab-safe_request_store/Gemfile.lock @@ -0,0 +1,103 @@ +PATH + remote: . + specs: + gitlab-safe_request_store (0.1.0) + request_store + +GEM + remote: https://rubygems.org/ + specs: + activesupport (7.0.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + ast (2.4.2) + coderay (1.1.3) + concurrent-ruby (1.2.2) + diff-lcs (1.5.0) + gitlab-styles (10.1.0) + rubocop (~> 1.50.2) + rubocop-graphql (~> 0.18) + rubocop-performance (~> 1.15) + rubocop-rails (~> 2.17) + rubocop-rspec (~> 2.22) + i18n (1.12.0) + concurrent-ruby (~> 1.0) + json (2.6.3) + method_source (1.0.0) + minitest (5.17.0) + parallel (1.22.1) + parser (3.2.2.3) + ast (~> 2.4.1) + racc + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) + racc (1.6.2) + rack (3.0.4.1) + rainbow (3.1.1) + regexp_parser (2.7.0) + request_store (1.5.1) + rack (>= 1.4) + rexml (3.2.5) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.1) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.0) + rubocop (1.50.2) + json (~> 2.3) + parallel (~> 1.10) + parser (>= 3.2.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.28.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.29.0) + parser (>= 3.2.1.0) + rubocop-capybara (2.18.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.23.1) + rubocop (~> 1.33) + rubocop-graphql (0.19.0) + rubocop (>= 0.87, < 2) + rubocop-performance (1.18.0) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) + rubocop-rails (2.20.2) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.33.0, < 2.0) + rubocop-rspec (2.22.0) + rubocop (~> 1.33) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + ruby-progressbar (1.11.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (2.4.2) + +PLATFORMS + ruby + +DEPENDENCIES + gitlab-safe_request_store! + gitlab-styles (~> 10.1.0) + pry + rspec (~> 3.0) + rubocop (~> 1.50) + rubocop-rspec (~> 2.22) + +BUNDLED WITH + 2.4.16 diff --git a/gems/gitlab-safe_request_store/README.md b/gems/gitlab-safe_request_store/README.md new file mode 100644 index 00000000000..13c20d74364 --- /dev/null +++ b/gems/gitlab-safe_request_store/README.md @@ -0,0 +1,42 @@ +# Gitlab::SafeRequestStore + +A safer abstraction of `RequestStore` that comes with [`request_store` gem](https://github.com/steveklabnik/request_store). + +This gem works as a proxy to `RequestStore` allowing you to use the same interface even when the Request Store +is not active. In this case a `NullStore` is being used under the hood providing no-ops when the Request Store +is not active. + +## Usage + +When request store is active it works the same as `RequestStore`: + +```ruby +Gitlab::SafeRequestStore.active? # => true +Gitlab::SafeRequestStore[:test] = 123 +Gitlab::SafeRequestStore[:test] # => 123 +``` + +When request store is not active it does nothing: + +```ruby +Gitlab::SafeRequestStore.active? # => false +Gitlab::SafeRequestStore[:test] = 123 +Gitlab::SafeRequestStore[:test] # => nil +``` + +You can enforce the request store to temporarily be active using: + +```ruby +Gitlab::SafeRequestStore.active? # => false + +Gitlab::SafeRequestStore.ensure_request_store do + Gitlab::SafeRequestStore.active? # => true + # do something... +end + +Gitlab::SafeRequestStore.active? # => false +``` + +## Development + +Follow the GitLab [gems development guidelines](../../doc/development/gems.md). diff --git a/gems/gitlab-safe_request_store/gitlab-safe_request_store.gemspec b/gems/gitlab-safe_request_store/gitlab-safe_request_store.gemspec new file mode 100644 index 00000000000..a685a1eb447 --- /dev/null +++ b/gems/gitlab-safe_request_store/gitlab-safe_request_store.gemspec @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative "lib/gitlab/safe_request_store/version" + +Gem::Specification.new do |spec| + spec.name = "gitlab-safe_request_store" + spec.version = Gitlab::SafeRequestStore::Version::VERSION + spec.authors = ["group::tenant-scale"] + spec.email = ["engineering@gitlab.com"] + + spec.summary = "Wrapper for RequestStore" + spec.description = "Provides a safe interface for RequestStore even when it is not active." + spec.homepage = "https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/gitlab-safe_request_store" + spec.license = 'MIT' + spec.required_ruby_version = ">= 3.0" + spec.metadata["rubygems_mfa_required"] = "true" + + spec.files = Dir['lib/**/*.rb'] + spec.require_paths = ["lib"] + + spec.add_runtime_dependency "request_store" + + spec.add_development_dependency "gitlab-styles", "~> 10.1.0" + spec.add_development_dependency "pry" + spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "rubocop", "~> 1.50" + spec.add_development_dependency "rubocop-rspec", "~> 2.22" +end diff --git a/gems/gitlab-safe_request_store/lib/gitlab/safe_request_store.rb b/gems/gitlab-safe_request_store/lib/gitlab/safe_request_store.rb new file mode 100644 index 00000000000..65594283fb8 --- /dev/null +++ b/gems/gitlab-safe_request_store/lib/gitlab/safe_request_store.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'forwardable' + +require 'request_store' + +require_relative "safe_request_store/version" +require_relative "safe_request_store/null_store" + +module Gitlab + module SafeRequestStore + NULL_STORE = NullStore.new + + class << self + extend Forwardable + + # These methods should always run directly against RequestStore + def_delegators :RequestStore, :clear!, :begin!, :end!, :active? + + # These methods will run against NullStore if RequestStore is disabled + def_delegators :store, :read, :[], :write, :[]=, :exist?, :fetch, :delete + + def store + if RequestStore.active? + RequestStore + else + NULL_STORE + end + end + + # Access to the backing storage of the request store. This returns an object + # with `[]` and `[]=` methods that does not discard values. + # + # This can be useful if storage is needed for a delimited purpose, and the + # forgetful nature of the null store is undesirable. + def storage + store.store + end + + # This method accept an options hash to be compatible with + # ActiveSupport::Cache::Store#write method. The options are + # not passed to the underlying cache implementation because + # RequestStore#write accepts only a key, and value params. + def write(key, value, _options = nil) + store.write(key, value) + end + + def delete_if(&_block) + return unless RequestStore.active? + + storage.delete_if { |k, _v| yield(k) } + end + + def ensure_request_store(&block) + # Skip enabling the request store if it was already active. Whatever + # instantiated the request store first is responsible for clearing it + return yield if RequestStore.active? + + enabling_request_store(&block) + end + + private + + def enabling_request_store + RequestStore.begin! + yield + ensure + RequestStore.end! + RequestStore.clear! + end + end + end +end diff --git a/gems/gitlab-safe_request_store/lib/gitlab/safe_request_store/null_store.rb b/gems/gitlab-safe_request_store/lib/gitlab/safe_request_store/null_store.rb new file mode 100644 index 00000000000..9a75ce6421c --- /dev/null +++ b/gems/gitlab-safe_request_store/lib/gitlab/safe_request_store/null_store.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Gitlab + module SafeRequestStore + # The methods `begin!`, `clear!`, and `end!` are not defined because they + # should only be called directly on `RequestStore`. + class NullStore + def store + {} + end + + def active? + # no-op + end + + def read(_key) + # no-op + end + + def [](_key) + # no-op + end + + def write(_key, value) + value + end + + def []=(_key, value) + value + end + + def exist?(_key) + false + end + + def fetch(_key, &_block) + yield + end + + def delete(key, &block) + yield(key) if block + end + end + end +end diff --git a/gems/gitlab-safe_request_store/lib/gitlab/safe_request_store/version.rb b/gems/gitlab-safe_request_store/lib/gitlab/safe_request_store/version.rb new file mode 100644 index 00000000000..c7eecf1efe0 --- /dev/null +++ b/gems/gitlab-safe_request_store/lib/gitlab/safe_request_store/version.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Gitlab + module SafeRequestStore + module Version + VERSION = "0.1.0" + end + end +end diff --git a/gems/gitlab-safe_request_store/spec/gitlab/safe_request_store/null_store_spec.rb b/gems/gitlab-safe_request_store/spec/gitlab/safe_request_store/null_store_spec.rb new file mode 100644 index 00000000000..a6bd4bba50f --- /dev/null +++ b/gems/gitlab-safe_request_store/spec/gitlab/safe_request_store/null_store_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::SafeRequestStore::NullStore do + let(:null_store) { described_class.new } + + describe '#store' do + it 'returns an empty hash' do + expect(null_store.store).to eq({}) + end + end + + describe '#active?' do + it 'returns falsey' do + expect(null_store.active?).to be_falsey + end + end + + describe '#read' do + it 'returns nil' do + expect(null_store.read('foo')).to be nil + end + end + + describe '#[]' do + it 'returns nil' do + expect(null_store['foo']).to be nil + end + end + + describe '#write' do + it 'returns the same value' do + expect(null_store.write('key', 'value')).to eq('value') + end + end + + describe '#[]=' do + it 'returns the same value' do + expect(null_store['key'] = 'value').to eq('value') + end + end + + describe '#exist?' do + it 'returns falsey' do + expect(null_store.exist?('foo')).to be_falsey + end + end + + describe '#fetch' do + it 'returns the block result' do + expect(null_store.fetch('key') { 'block result' }).to eq('block result') # rubocop:disable Style/RedundantFetchBlock + end + end + + describe '#delete' do + context 'when a block is given' do + it 'yields the key to the block' do + expect do |b| + null_store.delete('foo', &b) + end.to yield_with_args('foo') + end + + it 'returns the block result' do + expect(null_store.delete('foo') { |_key| 'block result' }).to eq('block result') + end + end + + context 'when a block is not given' do + it 'returns nil' do + expect(null_store.delete('foo')).to be nil + end + end + end +end diff --git a/gems/gitlab-safe_request_store/spec/gitlab/safe_request_store_spec.rb b/gems/gitlab-safe_request_store/spec/gitlab/safe_request_store_spec.rb new file mode 100644 index 00000000000..a171c50ca3e --- /dev/null +++ b/gems/gitlab-safe_request_store/spec/gitlab/safe_request_store_spec.rb @@ -0,0 +1,281 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::SafeRequestStore do + it "has a version number" do + expect(described_class::Version::VERSION).not_to be nil + end + + describe '.ensure_request_store' do + it 'starts a request store and yields control' do + expect(RequestStore).to receive(:begin!).ordered + expect(RequestStore).to receive(:end!).ordered + expect(RequestStore).to receive(:clear!).ordered + + expect { |b| described_class.ensure_request_store(&b) }.to yield_control + end + + it 'only starts a request store once when nested' do + expect(RequestStore).to receive(:begin!).ordered.once.and_call_original + expect(RequestStore).to receive(:end!).ordered.once.and_call_original + expect(RequestStore).to receive(:clear!).ordered.once.and_call_original + + described_class.ensure_request_store do + expect { |b| described_class.ensure_request_store(&b) }.to yield_control + end + end + end + + describe '.store' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + expect(described_class.store).to eq(RequestStore) + end + end + + context 'when RequestStore is NOT active' do + it 'does not use RequestStore' do + expect(described_class.store).to be_a(described_class::NullStore) + end + end + end + + describe '.begin!' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + expect(RequestStore).to receive(:begin!) + + described_class.begin! + end + end + + context 'when RequestStore is NOT active' do + it 'uses RequestStore' do + expect(RequestStore).to receive(:begin!) + + described_class.begin! + end + end + end + + describe '.clear!' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + expect(RequestStore).to receive(:clear!).once.and_call_original + + described_class.clear! + end + end + + context 'when RequestStore is NOT active' do + it 'uses RequestStore' do + expect(RequestStore).to receive(:clear!).and_call_original + + described_class.clear! + end + end + end + + describe '.end!' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + expect(RequestStore).to receive(:end!).once.and_call_original + + described_class.end! + end + end + + context 'when RequestStore is NOT active' do + it 'uses RequestStore' do + expect(RequestStore).to receive(:end!).and_call_original + + described_class.end! + end + end + end + + describe '.write' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + expect do + described_class.write('foo', true) + end.to change { described_class.read('foo') }.from(nil).to(true) + end + + it 'does not pass the options hash to the underlying store implementation' do + expect(described_class.store).to receive(:write).with('foo', true) + + described_class.write('foo', true, expires_in: 15) + end + end + + context 'when RequestStore is NOT active' do + it 'does not use RequestStore' do + expect do + described_class.write('foo', true) + end.not_to change { described_class.read('foo') }.from(nil) + end + + it 'does not pass the options hash to the underlying store implementation' do + expect(described_class.store).to receive(:write).with('foo', true) + + described_class.write('foo', true, expires_in: 15) + end + end + end + + describe '.[]=' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + expect do + described_class['foo'] = true + end.to change { described_class.read('foo') }.from(nil).to(true) + end + end + + context 'when RequestStore is NOT active' do + it 'does not use RequestStore' do + expect do + described_class['foo'] = true + end.not_to change { described_class.read('foo') }.from(nil) + end + end + end + + describe '.read' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + expect do + RequestStore.write('foo', true) + end.to change { described_class.read('foo') }.from(nil).to(true) + end + end + + context 'when RequestStore is NOT active' do + it 'does not use RequestStore' do + expect do + RequestStore.write('foo', true) + end.not_to change { described_class.read('foo') }.from(nil) + + RequestStore.clear! # Clean up + end + end + end + + describe '.[]' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + expect do + RequestStore.write('foo', true) + end.to change { described_class['foo'] }.from(nil).to(true) + end + end + + context 'when RequestStore is NOT active' do + it 'does not use RequestStore' do + expect do + RequestStore.write('foo', true) + end.not_to change { described_class['foo'] }.from(nil) + + RequestStore.clear! # Clean up + end + end + end + + describe '.exist?' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + expect do + RequestStore.write('foo', 'not nil') + end.to change { described_class.exist?('foo') }.from(false).to(true) + end + end + + context 'when RequestStore is NOT active' do + it 'does not use RequestStore' do + expect do + RequestStore.write('foo', 'not nil') + end.not_to change { described_class.exist?('foo') }.from(false) + + RequestStore.clear! # Clean up + end + end + end + + describe '.fetch' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + expect do + described_class.fetch('foo') { 'block result' } # rubocop:disable Style/RedundantFetchBlock + end.to change { described_class.read('foo') }.from(nil).to('block result') + end + end + + context 'when RequestStore is NOT active' do + it 'does not use RequestStore' do + RequestStore.clear! # Ensure clean + + expect do + described_class.fetch('foo') { 'block result' } # rubocop:disable Style/RedundantFetchBlock + end.not_to change { described_class.read('foo') }.from(nil) + + RequestStore.clear! # Clean up + end + end + end + + describe '.delete' do + context 'when RequestStore is active', :enable_request_store do + it 'uses RequestStore' do + described_class.write('foo', true) + + expect do + described_class.delete('foo') + end.to change { described_class.read('foo') }.from(true).to(nil) + end + + context 'when given a block and the key exists' do + it 'does not execute the block' do + described_class.write('foo', true) + + expect do |b| + described_class.delete('foo', &b) + end.not_to yield_control + end + end + + context 'when given a block and the key does not exist' do + it 'yields the key and returns the block result' do + result = described_class.delete('foo') { |key| "#{key} block result" } + + expect(result).to eq('foo block result') + end + end + end + + context 'when RequestStore is NOT active' do + before do + RequestStore.write('foo', true) + end + + after do + RequestStore.clear! # Clean up + end + + it 'does not use RequestStore' do + expect do + described_class.delete('foo') + end.not_to change { RequestStore.read('foo') }.from(true) + end + + context 'when given a block' do + it 'yields the key and returns the block result' do + result = described_class.delete('foo') { |key| "#{key} block result" } + + expect(result).to eq('foo block result') + end + end + end + end +end diff --git a/gems/gitlab-safe_request_store/spec/spec_helper.rb b/gems/gitlab-safe_request_store/spec/spec_helper.rb new file mode 100644 index 00000000000..08663dd7cb1 --- /dev/null +++ b/gems/gitlab-safe_request_store/spec/spec_helper.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "gitlab/safe_request_store" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end + + config.around(:example, :enable_request_store) do |example| + ::Gitlab::SafeRequestStore.ensure_request_store { example.run } + end +end |