diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/graphql/types/snippets/blob_type_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/gitlab/redis/boolean_spec.rb | 150 | ||||
-rw-r--r-- | spec/lib/gitlab/repository_cache_adapter_spec.rb | 3 | ||||
-rw-r--r-- | spec/lib/gitlab/repository_hash_cache_spec.rb | 184 | ||||
-rw-r--r-- | spec/lib/quality/test_level_spec.rb | 4 | ||||
-rw-r--r-- | spec/models/repository_spec.rb | 59 | ||||
-rw-r--r-- | spec/presenters/snippet_blob_presenter_spec.rb | 12 | ||||
-rw-r--r-- | spec/requests/api/graphql/mutations/snippets/create_spec.rb | 6 | ||||
-rw-r--r-- | spec/requests/api/graphql/mutations/snippets/update_spec.rb | 6 | ||||
-rw-r--r-- | spec/services/users/destroy_service_spec.rb | 10 | ||||
-rw-r--r-- | spec/support/helpers/query_recorder.rb | 49 | ||||
-rw-r--r-- | spec/support_specs/helpers/active_record/query_recorder_spec.rb | 40 |
12 files changed, 474 insertions, 51 deletions
diff --git a/spec/graphql/types/snippets/blob_type_spec.rb b/spec/graphql/types/snippets/blob_type_spec.rb index a263fada644..b6253e96d60 100644 --- a/spec/graphql/types/snippets/blob_type_spec.rb +++ b/spec/graphql/types/snippets/blob_type_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe GitlabSchema.types['SnippetBlob'] do it 'has the correct fields' do - expected_fields = [:highlighted_data, :plain_highlighted_data, + expected_fields = [:rich_data, :plain_data, :raw_path, :size, :binary, :name, :path, :simple_viewer, :rich_viewer, :mode] diff --git a/spec/lib/gitlab/redis/boolean_spec.rb b/spec/lib/gitlab/redis/boolean_spec.rb new file mode 100644 index 00000000000..bfacf0c448b --- /dev/null +++ b/spec/lib/gitlab/redis/boolean_spec.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Gitlab::Redis::Boolean do + subject(:redis_boolean) { described_class.new(bool) } + + let(:bool) { true } + let(:label_section) { "#{described_class::LABEL}#{described_class::DELIMITER}" } + + describe "#to_s" do + subject { redis_boolean.to_s } + + context "true" do + let(:bool) { true } + + it { is_expected.to eq("#{label_section}#{described_class::TRUE_STR}") } + end + + context "false" do + let(:bool) { false } + + it { is_expected.to eq("#{label_section}#{described_class::FALSE_STR}") } + end + end + + describe ".encode" do + subject { redis_boolean.class.encode(bool) } + + context "true" do + let(:bool) { true } + + it { is_expected.to eq("#{label_section}#{described_class::TRUE_STR}") } + end + + context "false" do + let(:bool) { false } + + it { is_expected.to eq("#{label_section}#{described_class::FALSE_STR}") } + end + end + + describe ".decode" do + subject { redis_boolean.class.decode(str) } + + context "valid encoded bool" do + let(:str) { "#{label_section}#{bool_str}" } + + context "true" do + let(:bool_str) { described_class::TRUE_STR } + + it { is_expected.to be(true) } + end + + context "false" do + let(:bool_str) { described_class::FALSE_STR } + + it { is_expected.to be(false) } + end + end + + context "partially invalid bool" do + let(:str) { "#{label_section}whoops" } + + it "raises an error" do + expect { subject }.to raise_error(described_class::NotAnEncodedBooleanStringError) + end + end + + context "invalid encoded bool" do + let(:str) { "whoops" } + + it "raises an error" do + expect { subject }.to raise_error(described_class::NotAnEncodedBooleanStringError) + end + end + end + + describe ".true?" do + subject { redis_boolean.class.true?(str) } + + context "valid encoded bool" do + let(:str) { "#{label_section}#{bool_str}" } + + context "true" do + let(:bool_str) { described_class::TRUE_STR } + + it { is_expected.to be(true) } + end + + context "false" do + let(:bool_str) { described_class::FALSE_STR } + + it { is_expected.to be(false) } + end + end + + context "partially invalid bool" do + let(:str) { "#{label_section}whoops" } + + it "raises an error" do + expect { subject }.to raise_error(described_class::NotAnEncodedBooleanStringError) + end + end + + context "invalid encoded bool" do + let(:str) { "whoops" } + + it "raises an error" do + expect { subject }.to raise_error(described_class::NotAnEncodedBooleanStringError) + end + end + end + + describe ".false?" do + subject { redis_boolean.class.false?(str) } + + context "valid encoded bool" do + let(:str) { "#{label_section}#{bool_str}" } + + context "true" do + let(:bool_str) { described_class::TRUE_STR } + + it { is_expected.to be(false) } + end + + context "false" do + let(:bool_str) { described_class::FALSE_STR } + + it { is_expected.to be(true) } + end + end + + context "partially invalid bool" do + let(:str) { "#{label_section}whoops" } + + it "raises an error" do + expect { subject }.to raise_error(described_class::NotAnEncodedBooleanStringError) + end + end + + context "invalid encoded bool" do + let(:str) { "whoops" } + + it "raises an error" do + expect { subject }.to raise_error(described_class::NotAnEncodedBooleanStringError) + end + end + end +end diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb index fd1338b55a6..b4fc504ea60 100644 --- a/spec/lib/gitlab/repository_cache_adapter_spec.rb +++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb @@ -7,6 +7,7 @@ describe Gitlab::RepositoryCacheAdapter do let(:repository) { project.repository } let(:cache) { repository.send(:cache) } let(:redis_set_cache) { repository.send(:redis_set_cache) } + let(:redis_hash_cache) { repository.send(:redis_hash_cache) } describe '#cache_method_output', :use_clean_rails_memory_store_caching do let(:fallback) { 10 } @@ -212,6 +213,8 @@ describe Gitlab::RepositoryCacheAdapter do expect(cache).to receive(:expire).with(:branch_names) expect(redis_set_cache).to receive(:expire).with(:rendered_readme) expect(redis_set_cache).to receive(:expire).with(:branch_names) + expect(redis_hash_cache).to receive(:delete).with(:rendered_readme) + expect(redis_hash_cache).to receive(:delete).with(:branch_names) repository.expire_method_caches(%i(rendered_readme branch_names)) end diff --git a/spec/lib/gitlab/repository_hash_cache_spec.rb b/spec/lib/gitlab/repository_hash_cache_spec.rb new file mode 100644 index 00000000000..014a2f235b9 --- /dev/null +++ b/spec/lib/gitlab/repository_hash_cache_spec.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_cache do + let_it_be(:project) { create(:project) } + let(:repository) { project.repository } + let(:namespace) { "#{repository.full_path}:#{project.id}" } + let(:cache) { described_class.new(repository) } + let(:test_hash) do + { "test" => "value" } + end + + describe "#cache_key" do + subject { cache.cache_key(:example) } + + it "includes the namespace" do + is_expected.to eq("example:#{namespace}:hash") + end + + context "with a given namespace" do + let(:extra_namespace) { "my:data" } + let(:cache) { described_class.new(repository, extra_namespace: extra_namespace) } + + it "includes the full namespace" do + is_expected.to eq("example:#{namespace}:#{extra_namespace}:hash") + end + end + end + + describe "#delete" do + subject { cache.delete(:example) } + + context "key exists" do + before do + cache.write(:example, test_hash) + end + + it { is_expected.to eq(1) } + + it "deletes the given key from the cache" do + subject + + expect(cache.read_members(:example, ["test"])).to eq({ "test" => nil }) + end + end + + context "key doesn't exist" do + it { is_expected.to eq(0) } + end + end + + describe "#key?" do + subject { cache.key?(:example, "test") } + + context "key exists" do + before do + cache.write(:example, test_hash) + end + + it { is_expected.to be(true) } + end + + context "key doesn't exist" do + it { is_expected.to be(false) } + end + end + + describe "#read_members" do + subject { cache.read_members(:example, keys) } + + let(:keys) { %w(test missing) } + + context "all data is cached" do + before do + cache.write(:example, test_hash.merge({ "missing" => false })) + end + + it { is_expected.to eq({ "test" => "value", "missing" => "false" }) } + end + + context "partial data is cached" do + before do + cache.write(:example, test_hash) + end + + it { is_expected.to eq({ "test" => "value", "missing" => nil }) } + end + + context "no data is cached" do + it { is_expected.to eq({ "test" => nil, "missing" => nil }) } + end + + context "empty keys are passed for some reason" do + let(:keys) { [] } + + it "raises an error" do + expect { subject }.to raise_error(Gitlab::RepositoryHashCache::InvalidKeysProvidedError) + end + end + end + + describe "#write" do + subject { cache.write(:example, test_hash) } + + it { is_expected.to be(true) } + + it "actually writes stuff to Redis" do + subject + + expect(cache.read_members(:example, ["test"])).to eq(test_hash) + end + end + + describe "#fetch_and_add_missing" do + subject do + cache.fetch_and_add_missing(:example, keys) do |missing_keys, hash| + missing_keys.each do |key| + hash[key] = "was_missing" + end + end + end + + let(:keys) { %w(test) } + + it "records metrics" do + # Here we expect it to receive "test" as a missing key because we + # don't write to the cache before this test + expect(cache).to receive(:record_metrics).with(:example, { "test" => "was_missing" }, ["test"]) + + subject + end + + context "fully cached" do + let(:keys) { %w(test another) } + + before do + cache.write(:example, test_hash.merge({ "another" => "not_missing" })) + end + + it "returns a hash" do + is_expected.to eq({ "test" => "value", "another" => "not_missing" }) + end + + it "doesn't write to the cache" do + expect(cache).not_to receive(:write) + + subject + end + end + + context "partially cached" do + let(:keys) { %w(test missing) } + + before do + cache.write(:example, test_hash) + end + + it "returns a hash" do + is_expected.to eq({ "test" => "value", "missing" => "was_missing" }) + end + + it "writes to the cache" do + expect(cache).to receive(:write).with(:example, { "missing" => "was_missing" }) + + subject + end + end + + context "uncached" do + let(:keys) { %w(test missing) } + + it "returns a hash" do + is_expected.to eq({ "test" => "was_missing", "missing" => "was_missing" }) + end + + it "writes to the cache" do + expect(cache).to receive(:write).with(:example, { "test" => "was_missing", "missing" => "was_missing" }) + + subject + end + end + end +end diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb index 621a426a18d..757a003946b 100644 --- a/spec/lib/quality/test_level_spec.rb +++ b/spec/lib/quality/test_level_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Quality::TestLevel do context 'when level is unit' do it 'returns a pattern' do expect(subject.pattern(:unit)) - .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb") + .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,serializers,services,sidekiq,support_specs,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb") end end @@ -82,7 +82,7 @@ RSpec.describe Quality::TestLevel do context 'when level is unit' do it 'returns a regexp' do expect(subject.regexp(:unit)) - .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)}) + .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|serializers|services|sidekiq|support_specs|tasks|uploaders|validators|views|workers|elastic_integration)}) end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 1e558131dc6..77114696fd2 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -500,45 +500,62 @@ describe Repository do let(:branch_names) { %w(test beep boop definitely_merged) } let(:already_merged) { Set.new(["definitely_merged"]) } - let(:merge_state_hash) do + let(:write_hash) do { - "test" => false, - "beep" => false, - "boop" => false, - "definitely_merged" => true + "test" => Gitlab::Redis::Boolean.new(false).to_s, + "beep" => Gitlab::Redis::Boolean.new(false).to_s, + "boop" => Gitlab::Redis::Boolean.new(false).to_s, + "definitely_merged" => Gitlab::Redis::Boolean.new(true).to_s } end - let_it_be(:cache) do - caching_config_hash = Gitlab::Redis::Cache.params - ActiveSupport::Cache.lookup_store(:redis_cache_store, caching_config_hash) - end - - let(:repository_cache) do - Gitlab::RepositoryCache.new(repository, backend: Rails.cache) + let(:read_hash) do + { + "test" => Gitlab::Redis::Boolean.new(false).to_s, + "beep" => Gitlab::Redis::Boolean.new(false).to_s, + "boop" => Gitlab::Redis::Boolean.new(false).to_s, + "definitely_merged" => Gitlab::Redis::Boolean.new(true).to_s + } end - let(:cache_key) { repository_cache.cache_key(:merged_branch_names) } + let(:cache) { repository.send(:redis_hash_cache) } + let(:cache_key) { cache.cache_key(:merged_branch_names) } before do - allow(Rails).to receive(:cache) { cache } - allow(repository).to receive(:cache) { repository_cache } allow(repository.raw_repository).to receive(:merged_branch_names).with(branch_names).and_return(already_merged) end it { is_expected.to eq(already_merged) } it { is_expected.to be_a(Set) } + describe "cache expiry" do + before do + allow(cache).to receive(:delete).with(anything) + end + + it "is expired when the branches caches are expired" do + expect(cache).to receive(:delete).with(:merged_branch_names).at_least(:once) + + repository.send(:expire_branches_cache) + end + + it "is expired when the repository caches are expired" do + expect(cache).to receive(:delete).with(:merged_branch_names).at_least(:once) + + repository.send(:expire_all_method_caches) + end + end + context "cache is empty" do before do - cache.delete(cache_key) + cache.delete(:merged_branch_names) end it { is_expected.to eq(already_merged) } describe "cache values" do it "writes the values to redis" do - expect(cache).to receive(:write).with(cache_key, merge_state_hash, expires_in: Repository::MERGED_BRANCH_NAMES_CACHE_DURATION) + expect(cache).to receive(:write).with(:merged_branch_names, write_hash) subject end @@ -546,14 +563,14 @@ describe Repository do it "matches the supplied hash" do subject - expect(cache.read(cache_key)).to eq(merge_state_hash) + expect(cache.read_members(:merged_branch_names, branch_names)).to eq(read_hash) end end end context "cache is not empty" do before do - cache.write(cache_key, merge_state_hash) + cache.write(:merged_branch_names, write_hash) end it { is_expected.to eq(already_merged) } @@ -568,8 +585,8 @@ describe Repository do context "cache is partially complete" do before do allow(repository.raw_repository).to receive(:merged_branch_names).with(["boop"]).and_return([]) - hash = merge_state_hash.except("boop") - cache.write(cache_key, hash) + hash = write_hash.except("boop") + cache.write(:merged_branch_names, hash) end it { is_expected.to eq(already_merged) } diff --git a/spec/presenters/snippet_blob_presenter_spec.rb b/spec/presenters/snippet_blob_presenter_spec.rb index 92893ec597a..fa10d1a7f30 100644 --- a/spec/presenters/snippet_blob_presenter_spec.rb +++ b/spec/presenters/snippet_blob_presenter_spec.rb @@ -3,10 +3,10 @@ require 'spec_helper' describe SnippetBlobPresenter do - describe '#highlighted_data' do + describe '#rich_data' do let(:snippet) { build(:personal_snippet) } - subject { described_class.new(snippet.blob).highlighted_data } + subject { described_class.new(snippet.blob).rich_data } it 'returns nil when the snippet blob is binary' do allow(snippet.blob).to receive(:binary?).and_return(true) @@ -18,7 +18,7 @@ describe SnippetBlobPresenter do snippet.file_name = 'test.md' snippet.content = '*foo*' - expect(subject).to eq '<span id="LC1" class="line" lang="markdown"><span class="ge">*foo*</span></span>' + expect(subject).to eq '<p data-sourcepos="1:1-1:5" dir="auto"><em>foo</em></p>' end it 'returns syntax highlighted content' do @@ -37,10 +37,10 @@ describe SnippetBlobPresenter do end end - describe '#plain_highlighted_data' do + describe '#plain_data' do let(:snippet) { build(:personal_snippet) } - subject { described_class.new(snippet.blob).plain_highlighted_data } + subject { described_class.new(snippet.blob).plain_data } it 'returns nil when the snippet blob is binary' do allow(snippet.blob).to receive(:binary?).and_return(true) @@ -52,7 +52,7 @@ describe SnippetBlobPresenter do snippet.file_name = 'test.md' snippet.content = '*foo*' - expect(subject).to eq '<span id="LC1" class="line" lang="">*foo*</span>' + expect(subject).to eq '<span id="LC1" class="line" lang="markdown"><span class="ge">*foo*</span></span>' end it 'returns plain syntax content' do diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb index 876eff8c753..cb19f50b5b5 100644 --- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb @@ -67,7 +67,8 @@ describe 'Creating a Snippet' do it 'returns the created Snippet' do post_graphql_mutation(mutation, current_user: current_user) - expect(mutation_response['snippet']['blob']['highlightedData']).to match(content) + expect(mutation_response['snippet']['blob']['richData']).to match(content) + expect(mutation_response['snippet']['blob']['plainData']).to match(content) expect(mutation_response['snippet']['title']).to eq(title) expect(mutation_response['snippet']['description']).to eq(description) expect(mutation_response['snippet']['fileName']).to eq(file_name) @@ -92,7 +93,8 @@ describe 'Creating a Snippet' do it 'returns the created Snippet' do post_graphql_mutation(mutation, current_user: current_user) - expect(mutation_response['snippet']['blob']['highlightedData']).to match(content) + expect(mutation_response['snippet']['blob']['richData']).to match(content) + expect(mutation_response['snippet']['blob']['plainData']).to match(content) expect(mutation_response['snippet']['title']).to eq(title) expect(mutation_response['snippet']['description']).to eq(description) expect(mutation_response['snippet']['fileName']).to eq(file_name) diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb index f4c0b646c01..e9481a36287 100644 --- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb @@ -56,7 +56,8 @@ describe 'Updating a Snippet' do it 'returns the updated Snippet' do post_graphql_mutation(mutation, current_user: current_user) - expect(mutation_response['snippet']['blob']['highlightedData']).to match(updated_content) + expect(mutation_response['snippet']['blob']['richData']).to match(updated_content) + expect(mutation_response['snippet']['blob']['plainData']).to match(updated_content) expect(mutation_response['snippet']['title']).to eq(updated_title) expect(mutation_response['snippet']['description']).to eq(updated_description) expect(mutation_response['snippet']['fileName']).to eq(updated_file_name) @@ -77,7 +78,8 @@ describe 'Updating a Snippet' do it 'returns the Snippet with its original values' do post_graphql_mutation(mutation, current_user: current_user) - expect(mutation_response['snippet']['blob']['highlightedData']).to match(original_content) + expect(mutation_response['snippet']['blob']['richData']).to match(original_content) + expect(mutation_response['snippet']['blob']['plainData']).to match(original_content) expect(mutation_response['snippet']['title']).to eq(original_title) expect(mutation_response['snippet']['description']).to eq(original_description) expect(mutation_response['snippet']['fileName']).to eq(original_file_name) diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index f972fb4c5b9..2b658a93b0a 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -26,16 +26,6 @@ describe Users::DestroyService do service.execute(user) end - context 'when :destroy_user_associations_in_batches flag is disabled' do - it 'does not delete user associations in batches' do - stub_feature_flags(destroy_user_associations_in_batches: false) - - expect(user).not_to receive(:destroy_dependent_associations_in_batches) - - service.execute(user) - end - end - it 'will delete the project' do expect_next_instance_of(Projects::DestroyService) do |destroy_service| expect(destroy_service).to receive(:execute).once.and_return(true) diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb index 1d04014c9a6..fd200a1abf3 100644 --- a/spec/support/helpers/query_recorder.rb +++ b/spec/support/helpers/query_recorder.rb @@ -2,12 +2,15 @@ module ActiveRecord class QueryRecorder - attr_reader :log, :skip_cached, :cached + attr_reader :log, :skip_cached, :cached, :data + UNKNOWN = %w(unknown unknown).freeze - def initialize(skip_cached: true, &block) + def initialize(skip_cached: true, query_recorder_debug: false, &block) + @data = Hash.new { |h, k| h[k] = { count: 0, occurrences: [], backtrace: [] } } @log = [] @cached = [] @skip_cached = skip_cached + @query_recorder_debug = query_recorder_debug # force replacement of bind parameters to give tests the ability to check for ids ActiveRecord::Base.connection.unprepared_statement do ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block) @@ -19,30 +22,62 @@ module ActiveRecord Gitlab::BacktraceCleaner.clean_backtrace(caller).each { |line| Rails.logger.debug(" --> #{line}") } end + def get_sql_source(sql) + matches = sql.match(/,line:(?<line>.*):in\s+`(?<method>.*)'\*\//) + matches ? [matches[:line], matches[:method]] : UNKNOWN + end + + def store_sql_by_source(values: {}, backtrace: nil) + full_name = get_sql_source(values[:sql]).join(':') + @data[full_name][:count] += 1 + @data[full_name][:occurrences] << values[:sql] + @data[full_name][:backtrace] << backtrace + end + + def find_query(query_regexp, limit, first_only: false) + out = [] + + @data.each_pair do |k, v| + if v[:count] > limit && k.match(query_regexp) + out << [k, v[:count]] + break if first_only + end + end + + out.flatten! if first_only + out + end + + def occurrences_by_line_method + @occurrences_by_line_method ||= @data.sort_by { |_, v| v[:count] } + end + def callback(name, start, finish, message_id, values) - show_backtrace(values) if ENV['QUERY_RECORDER_DEBUG'] + store_backtrace = ENV['QUERY_RECORDER_DEBUG'] || @query_recorder_debug + backtrace = store_backtrace ? show_backtrace(values) : nil if values[:cached] && skip_cached @cached << values[:sql] elsif !values[:name]&.include?("SCHEMA") @log << values[:sql] + store_sql_by_source(values: values, backtrace: backtrace) end end def count - @log.count + @count ||= @log.count end def cached_count - @cached.count + @cached_count ||= @cached.count end def log_message - @log.join("\n\n") + @log_message ||= @log.join("\n\n") end def occurrences - @log.group_by(&:to_s).transform_values(&:count) + @occurrences ||= @log.group_by(&:to_s).transform_values(&:count) end end end diff --git a/spec/support_specs/helpers/active_record/query_recorder_spec.rb b/spec/support_specs/helpers/active_record/query_recorder_spec.rb new file mode 100644 index 00000000000..48069c6a766 --- /dev/null +++ b/spec/support_specs/helpers/active_record/query_recorder_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ActiveRecord::QueryRecorder do + class TestQueries < ActiveRecord::Base + self.table_name = 'schema_migrations' + end + + describe 'detecting the right number of calls and their origin' do + it 'detects two separate queries' do + control = ActiveRecord::QueryRecorder.new query_recorder_debug: true do + 2.times { TestQueries.count } + TestQueries.first + end + + # Test first_only flag works as expected + expect(control.find_query(/.*query_recorder_spec.rb.*/, 0, first_only: true)) + .to eq(control.find_query(/.*query_recorder_spec.rb.*/, 0).first) + # Check #find_query + expect(control.find_query(/.*/, 0).size) + .to eq(control.data.keys.size) + # Ensure exactly 2 COUNT queries were detected + expect(control.occurrences_by_line_method.last[1][:occurrences] + .find_all {|i| i.match(/SELECT COUNT/) }.count).to eq(2) + # Ensure exactly 1 LIMIT 1 (#first) + expect(control.occurrences_by_line_method.first[1][:occurrences] + .find_all { |i| i.match(/ORDER BY.*#{TestQueries.table_name}.*LIMIT 1/) }.count).to eq(1) + + # Ensure 3 DB calls overall were executed + expect(control.log.size).to eq(3) + # Ensure memoization value match the raw value above + expect(control.count).to eq(control.log.size) + # Ensure we have only two sources of queries + expect(control.data.keys.size).to eq(2) + # Ensure we detect only queries from this file + expect(control.data.keys.find_all { |i| i.match(/query_recorder_spec.rb/) }.count).to eq(2) + end + end +end |