Loading lib/labkit/covered_experience/experience.rb +3 −2 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ module Labkit def start @started = Time.now.utc checkpoint_counter.increment(checkpoint: "start") # complete return self unless block_given? Loading Loading @@ -65,12 +66,12 @@ module Labkit @has_error end private def base_labels @base_labels ||= @definition.to_h.slice(:id, :feature_category, :urgency) end private def ensure_started! return @started unless @started.nil? Loading spec/labkit/covered_experience/experience_spec.rb +45 −127 Original line number Diff line number Diff line Loading @@ -3,17 +3,13 @@ require 'spec_helper' require 'labkit/covered_experience/registry' require 'labkit/covered_experience/experience' require_relative '../../support/covered_experience/matchers' RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do include StubENV let(:definition) do Labkit::CoveredExperience::Definition.new( id: 'testing_sample', description: 'Test experience for specs', feature_category: 'test_category', urgency: 'sync_fast' ) Labkit::CoveredExperience::Registry.new[:testing_sample] end subject(:experience) { described_class.new(definition) } Loading @@ -28,171 +24,113 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do expect do |block| experience.start(&block) end.to yield_with_args(experience) .and change { checkpoint&.get(experience.base_labels.merge(checkpoint: "start")).to_i }.by(1) .and change { checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i }.by(1) .and change { total&.get(experience.base_labels.merge(error: false)).to_i }.by(1) .and change { apdex&.get(experience.base_labels.merge(success: true)).to_i }.by(1) .and start_covered_experience(:testing_sample) .and complete_covered_experience(:testing_sample) end it 'captures exceptions and marks as error' do expect do experience.start { raise 'Something went wrong' } end.to raise_error(RuntimeError, 'Something went wrong') .and change { checkpoint&.get(experience.base_labels.merge(checkpoint: "start")).to_i }.by(1) .and change { checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i }.by(1) .and change { # rubocop:disable RSpec/ChangeByZero, do not increment apdex for errors apdex&.get(experience.base_labels.merge(success: true)).to_i }.by(0) .and change { total&.get(experience.base_labels.merge(error: true)).to_i }.by(1) .and complete_covered_experience(:testing_sample, error: true) end end context 'when block is not given' do it 'returns itself' do expect(experience.start).to be(experience) end it 'marks an experience as started' do expect do experience.start end.to change { checkpoint&.get(experience.base_labels.merge(checkpoint: "start")).to_i }.by(1) subject(:start) { experience.start } # do not end the experience expect(checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i).to eq 0 expect(total&.get(experience.base_labels.merge(error: false)).to_i).to eq 0 expect(apdex&.get(experience.base_labels.merge(success: true)).to_i).to eq 0 end it { is_expected.to be(experience) } it { expect { start }.to start_covered_experience(:testing_sample) } it { expect { start }.not_to complete_covered_experience(:testing_sample) } end end describe '#checkpoint' do subject(:checkpoint) { experience.checkpoint } context 'when started' do before do experience.start end it 'returns itself' do expect(experience.checkpoint).to be(experience) end it 'records an intermediate checkpoint' do expect do experience.checkpoint end.to change { checkpoint&.get(experience.base_labels.merge(checkpoint: "intermediate")).to_i }.by(1) end it { is_expected.to be(experience) } it { expect { checkpoint }.to checkpoint_covered_experience(:testing_sample) } end context 'when not started' do it 'does not complete when RAILS_ENV is production' do context 'when RAILS_ENV is production' do before do stub_env('RAILS_ENV', 'production') expect { experience.checkpoint }.not_to raise_error expect(checkpoint&.get(experience.base_labels.merge(checkpoint: "intermediate")).to_i).to eq 0 end it 'does not complete when RAILS_ENV is unset' do expect { experience.checkpoint }.not_to raise_error it { expect { checkpoint }.not_to raise_error } it { expect { checkpoint }.not_to checkpoint_covered_experience(:testing_sample) } end expect(checkpoint&.get(experience.base_labels.merge(checkpoint: "intermediate")).to_i).to eq 0 context 'when RAILS_ENV is unset' do it { expect { checkpoint }.not_to raise_error } it { expect { checkpoint }.not_to checkpoint_covered_experience(:testing_sample) } end %w[test development].each do |env| it "raises error when RAILS_ENV is #{env}" do context "when RAILS_ENV is #{env}" do before do stub_env('RAILS_ENV', env) end expect { experience.checkpoint }.to raise_error(RuntimeError, "Covered Experience #{definition.id} not started") it { expect { checkpoint }.to raise_error(RuntimeError, "Covered Experience #{definition.id} not started") } end end end end describe '#complete' do subject(:complete) { experience.complete } context 'when started' do before do experience.start end it 'returns itself' do expect(experience.complete).to be(experience) end it { is_expected.to be(experience) } it { expect { complete }.to complete_covered_experience(:testing_sample) } it 'records its full completeness' do expect do experience.complete end.to change { checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i }.by(1) .and change { total&.get(experience.base_labels.merge(error: false)).to_i }.by(1) .and change { apdex&.get(experience.base_labels.merge(success: true)).to_i }.by(1) end it 'ends with error if previously marked as error' do it 'ends with error when marked as error' do expect do experience.error!.complete end.to change { total&.get(experience.base_labels.merge(error: true)).to_i }.by(1) end.to complete_covered_experience(:testing_sample, error: true) end it 'ends with apdex failure if time elapsed is too long' do it 'ends with apdex failure when time elapsed is too long' do # simulate the elapsed time in the future expect(Time).to receive(:now).and_return(Time.now.utc + 60) expect do experience.complete end.to change { apdex&.get(experience.base_labels.merge(success: false)).to_i }.by(1) expect { complete }.to complete_covered_experience(:testing_sample, success: false) end end context 'when not started' do it 'does not complete when RAILS_ENV is production' do context 'when RAILS_ENV is production' do before do stub_env('RAILS_ENV', 'production') expect { experience.complete }.not_to raise_error expect(checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i).to eq 0 expect(total&.get(experience.base_labels.merge(error: false)).to_i).to eq 0 expect(apdex&.get(experience.base_labels.merge(success: true)).to_i).to eq 0 end it 'does not complete when RAILS_ENV is unset' do expect { experience.complete }.not_to raise_error it { expect { complete }.not_to raise_error } it { expect { complete }.not_to complete_covered_experience(:testing_sample) } end expect(checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i).to eq 0 expect(total&.get(experience.base_labels.merge(error: false)).to_i).to eq 0 expect(apdex&.get(experience.base_labels.merge(success: true)).to_i).to eq 0 context 'when RAILS_ENV is unset' do it { expect { complete }.not_to raise_error } it { expect { complete }.not_to complete_covered_experience(:testing_sample) } end %w[test development].each do |env| it "raises error when RAILS_ENV is #{env}" do context "when RAILS_ENV is #{env}" do before do stub_env('RAILS_ENV', env) end expect { experience.complete }.to raise_error(RuntimeError, "Covered Experience #{definition.id} not started") it { expect { complete }.to raise_error(RuntimeError, "Covered Experience #{definition.id} not started") } end end end Loading @@ -204,24 +142,4 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do expect(experience).to have_error end end describe '#base_labels' do it 'returns base labels extracted from the definition' do expect(experience.base_labels.keys).to include(:id, :feature_category, :urgency) end end private def checkpoint Labkit::Metrics::Client.get(:gitlab_covered_experience_checkpoint_total) end def total Labkit::Metrics::Client.get(:gitlab_covered_experience_total) end def apdex Labkit::Metrics::Client.get(:gitlab_covered_experience_apdex_total) end end spec/support/covered_experience/matchers.rb 0 → 100644 +184 −0 Original line number Diff line number Diff line # frozen_string_literal: true module CoveredExperienceMetrics def checkpoint_counter Labkit::Metrics::Client.get(:gitlab_covered_experience_checkpoint_total) end def total_counter Labkit::Metrics::Client.get(:gitlab_covered_experience_total) end def apdex_counter Labkit::Metrics::Client.get(:gitlab_covered_experience_apdex_total) end def base_labels(covered_experience_id) raise ArgumentError, "covered_experience_id is required" if covered_experience_id.nil? definition = Labkit::CoveredExperience::Registry.new[covered_experience_id] definition.to_h.slice(:id, :feature_category, :urgency) end end # Matcher for verifying CoveredExperience start metrics instrumentation. # # Usage: # expect { subject }.to start_covered_experience('rails_request') # # This matcher verifies that the following metric is incremented: # - gitlab_covered_experience_checkpoint_total (with checkpoint=start) # # Parameters: # - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request') RSpec::Matchers.define :start_covered_experience do |covered_experience_id| include CoveredExperienceMetrics description { "start covered experience '#{covered_experience_id}'" } supports_block_expectations match do |actual| labels = base_labels(covered_experience_id) checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i actual.call checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i @checkpoint_change = checkpoint_after - checkpoint_before @checkpoint_change == 1 end failure_message do "Failed to checkpoint covered experience '#{covered_experience_id}':\n" \ "expected checkpoint='start' counter to increase by 1, but increased by #{@checkpoint_change}" end end # Matcher for verifying CoveredExperience checkpoint metrics instrumentation. # # Usage: # expect { subject }.to checkpoint_covered_experience('rails_request') # # This matcher verifies that the following metric is incremented: # - gitlab_covered_experience_checkpoint_total (with checkpoint=intermediate) # # Parameters: # - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request') RSpec::Matchers.define :checkpoint_covered_experience do |covered_experience_id| include CoveredExperienceMetrics description { "checkpoint covered experience '#{covered_experience_id}'" } supports_block_expectations match do |actual| labels = base_labels(covered_experience_id) checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i actual.call checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i @checkpoint_change = checkpoint_after - checkpoint_before @checkpoint_change == 1 end failure_message do "Failed to checkpoint covered experience '#{covered_experience_id}':\n" \ "expected checkpoint='intermediate' counter to increase by 1, but increased by #{@checkpoint_change}" end match_when_negated do |actual| labels = base_labels(covered_experience_id) checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i actual.call checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i @checkpoint_change = checkpoint_after - checkpoint_before @checkpoint_change == 0 end failure_message_when_negated do "Expected covered experience '#{covered_experience_id}' NOT to checkpoint:\n" \ "expected checkpoint='intermediate' counter to increase by 0, but increased by #{@checkpoint_change}" end end # Matcher for verifying CoveredExperience completion metrics instrumentation. # # Usage: # expect { subject }.to covered_experience_completed('rails_request') # # This matcher verifies that the following metrics are incremented with specific labels: # - gitlab_covered_experience_checkpoint_total (with checkpoint=end) # - gitlab_covered_experience_total (with error=false) # - gitlab_covered_experience_apdex_total (with success=true) # # Parameters: # - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request') # - error: Optional. The expected error flag for gitlab_covered_experience_total (false by default) # - error: Optional. The expected success flag for gitlab_covered_experience_apdex_total (true by default) RSpec::Matchers.define :complete_covered_experience do |covered_experience_id, error: false, success: true| include CoveredExperienceMetrics description { "complete covered experience '#{covered_experience_id}'" } supports_block_expectations match do |actual| labels = base_labels(covered_experience_id) checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i total_before = total_counter&.get(labels.merge(error: error)).to_i apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i actual.call checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i total_after = total_counter&.get(labels.merge(error: error)).to_i apdex_after = apdex_counter&.get(labels.merge(success: success)).to_i @checkpoint_change = checkpoint_after - checkpoint_before @total_change = total_after - total_before @apdex_change = apdex_after - apdex_before @checkpoint_change == 1 && @total_change == 1 && @apdex_change == (error ? 0 : 1) end failure_message do "Failed to complete covered experience '#{covered_experience_id}':\n" \ "expected checkpoint='end' counter to increase by 1, but increased by #{@checkpoint_change}\n" \ "expected total='error: #{error}' counter to increase by 1, but increased by #{@total_change}\n" \ "expected apdex='success: #{success}' counter to increase by 1, but increased by #{@apdex_change}" end match_when_negated do |actual| labels = base_labels(covered_experience_id) checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i total_before = total_counter&.get(labels.merge(error: error)).to_i apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i actual.call checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i total_after = total_counter&.get(labels.merge(error: error)).to_i apdex_after = apdex_counter&.get(labels.merge(success: success)).to_i @checkpoint_change = checkpoint_after - checkpoint_before @total_change = total_after - total_before @apdex_change = apdex_after - apdex_before @checkpoint_change == 0 && @total_change == 0 && @apdex_change == (error ? 1 : 0) end failure_message_when_negated do "Failed covered experience '#{covered_experience_id}' NOT to complete:\n" \ "expected checkpoint='end' counter to increase by 0, but increased by #{@checkpoint_change}\n" \ "expected total='error: #{error}' counter to increase by 0, but increased by #{@total_change}\n" \ "expected apdex='success: #{success}' counter to increase by 0, but increased by #{@apdex_change}" end end Loading
lib/labkit/covered_experience/experience.rb +3 −2 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ module Labkit def start @started = Time.now.utc checkpoint_counter.increment(checkpoint: "start") # complete return self unless block_given? Loading Loading @@ -65,12 +66,12 @@ module Labkit @has_error end private def base_labels @base_labels ||= @definition.to_h.slice(:id, :feature_category, :urgency) end private def ensure_started! return @started unless @started.nil? Loading
spec/labkit/covered_experience/experience_spec.rb +45 −127 Original line number Diff line number Diff line Loading @@ -3,17 +3,13 @@ require 'spec_helper' require 'labkit/covered_experience/registry' require 'labkit/covered_experience/experience' require_relative '../../support/covered_experience/matchers' RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do include StubENV let(:definition) do Labkit::CoveredExperience::Definition.new( id: 'testing_sample', description: 'Test experience for specs', feature_category: 'test_category', urgency: 'sync_fast' ) Labkit::CoveredExperience::Registry.new[:testing_sample] end subject(:experience) { described_class.new(definition) } Loading @@ -28,171 +24,113 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do expect do |block| experience.start(&block) end.to yield_with_args(experience) .and change { checkpoint&.get(experience.base_labels.merge(checkpoint: "start")).to_i }.by(1) .and change { checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i }.by(1) .and change { total&.get(experience.base_labels.merge(error: false)).to_i }.by(1) .and change { apdex&.get(experience.base_labels.merge(success: true)).to_i }.by(1) .and start_covered_experience(:testing_sample) .and complete_covered_experience(:testing_sample) end it 'captures exceptions and marks as error' do expect do experience.start { raise 'Something went wrong' } end.to raise_error(RuntimeError, 'Something went wrong') .and change { checkpoint&.get(experience.base_labels.merge(checkpoint: "start")).to_i }.by(1) .and change { checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i }.by(1) .and change { # rubocop:disable RSpec/ChangeByZero, do not increment apdex for errors apdex&.get(experience.base_labels.merge(success: true)).to_i }.by(0) .and change { total&.get(experience.base_labels.merge(error: true)).to_i }.by(1) .and complete_covered_experience(:testing_sample, error: true) end end context 'when block is not given' do it 'returns itself' do expect(experience.start).to be(experience) end it 'marks an experience as started' do expect do experience.start end.to change { checkpoint&.get(experience.base_labels.merge(checkpoint: "start")).to_i }.by(1) subject(:start) { experience.start } # do not end the experience expect(checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i).to eq 0 expect(total&.get(experience.base_labels.merge(error: false)).to_i).to eq 0 expect(apdex&.get(experience.base_labels.merge(success: true)).to_i).to eq 0 end it { is_expected.to be(experience) } it { expect { start }.to start_covered_experience(:testing_sample) } it { expect { start }.not_to complete_covered_experience(:testing_sample) } end end describe '#checkpoint' do subject(:checkpoint) { experience.checkpoint } context 'when started' do before do experience.start end it 'returns itself' do expect(experience.checkpoint).to be(experience) end it 'records an intermediate checkpoint' do expect do experience.checkpoint end.to change { checkpoint&.get(experience.base_labels.merge(checkpoint: "intermediate")).to_i }.by(1) end it { is_expected.to be(experience) } it { expect { checkpoint }.to checkpoint_covered_experience(:testing_sample) } end context 'when not started' do it 'does not complete when RAILS_ENV is production' do context 'when RAILS_ENV is production' do before do stub_env('RAILS_ENV', 'production') expect { experience.checkpoint }.not_to raise_error expect(checkpoint&.get(experience.base_labels.merge(checkpoint: "intermediate")).to_i).to eq 0 end it 'does not complete when RAILS_ENV is unset' do expect { experience.checkpoint }.not_to raise_error it { expect { checkpoint }.not_to raise_error } it { expect { checkpoint }.not_to checkpoint_covered_experience(:testing_sample) } end expect(checkpoint&.get(experience.base_labels.merge(checkpoint: "intermediate")).to_i).to eq 0 context 'when RAILS_ENV is unset' do it { expect { checkpoint }.not_to raise_error } it { expect { checkpoint }.not_to checkpoint_covered_experience(:testing_sample) } end %w[test development].each do |env| it "raises error when RAILS_ENV is #{env}" do context "when RAILS_ENV is #{env}" do before do stub_env('RAILS_ENV', env) end expect { experience.checkpoint }.to raise_error(RuntimeError, "Covered Experience #{definition.id} not started") it { expect { checkpoint }.to raise_error(RuntimeError, "Covered Experience #{definition.id} not started") } end end end end describe '#complete' do subject(:complete) { experience.complete } context 'when started' do before do experience.start end it 'returns itself' do expect(experience.complete).to be(experience) end it { is_expected.to be(experience) } it { expect { complete }.to complete_covered_experience(:testing_sample) } it 'records its full completeness' do expect do experience.complete end.to change { checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i }.by(1) .and change { total&.get(experience.base_labels.merge(error: false)).to_i }.by(1) .and change { apdex&.get(experience.base_labels.merge(success: true)).to_i }.by(1) end it 'ends with error if previously marked as error' do it 'ends with error when marked as error' do expect do experience.error!.complete end.to change { total&.get(experience.base_labels.merge(error: true)).to_i }.by(1) end.to complete_covered_experience(:testing_sample, error: true) end it 'ends with apdex failure if time elapsed is too long' do it 'ends with apdex failure when time elapsed is too long' do # simulate the elapsed time in the future expect(Time).to receive(:now).and_return(Time.now.utc + 60) expect do experience.complete end.to change { apdex&.get(experience.base_labels.merge(success: false)).to_i }.by(1) expect { complete }.to complete_covered_experience(:testing_sample, success: false) end end context 'when not started' do it 'does not complete when RAILS_ENV is production' do context 'when RAILS_ENV is production' do before do stub_env('RAILS_ENV', 'production') expect { experience.complete }.not_to raise_error expect(checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i).to eq 0 expect(total&.get(experience.base_labels.merge(error: false)).to_i).to eq 0 expect(apdex&.get(experience.base_labels.merge(success: true)).to_i).to eq 0 end it 'does not complete when RAILS_ENV is unset' do expect { experience.complete }.not_to raise_error it { expect { complete }.not_to raise_error } it { expect { complete }.not_to complete_covered_experience(:testing_sample) } end expect(checkpoint&.get(experience.base_labels.merge(checkpoint: "end")).to_i).to eq 0 expect(total&.get(experience.base_labels.merge(error: false)).to_i).to eq 0 expect(apdex&.get(experience.base_labels.merge(success: true)).to_i).to eq 0 context 'when RAILS_ENV is unset' do it { expect { complete }.not_to raise_error } it { expect { complete }.not_to complete_covered_experience(:testing_sample) } end %w[test development].each do |env| it "raises error when RAILS_ENV is #{env}" do context "when RAILS_ENV is #{env}" do before do stub_env('RAILS_ENV', env) end expect { experience.complete }.to raise_error(RuntimeError, "Covered Experience #{definition.id} not started") it { expect { complete }.to raise_error(RuntimeError, "Covered Experience #{definition.id} not started") } end end end Loading @@ -204,24 +142,4 @@ RSpec.describe Labkit::CoveredExperience::Experience, :with_metrics_config do expect(experience).to have_error end end describe '#base_labels' do it 'returns base labels extracted from the definition' do expect(experience.base_labels.keys).to include(:id, :feature_category, :urgency) end end private def checkpoint Labkit::Metrics::Client.get(:gitlab_covered_experience_checkpoint_total) end def total Labkit::Metrics::Client.get(:gitlab_covered_experience_total) end def apdex Labkit::Metrics::Client.get(:gitlab_covered_experience_apdex_total) end end
spec/support/covered_experience/matchers.rb 0 → 100644 +184 −0 Original line number Diff line number Diff line # frozen_string_literal: true module CoveredExperienceMetrics def checkpoint_counter Labkit::Metrics::Client.get(:gitlab_covered_experience_checkpoint_total) end def total_counter Labkit::Metrics::Client.get(:gitlab_covered_experience_total) end def apdex_counter Labkit::Metrics::Client.get(:gitlab_covered_experience_apdex_total) end def base_labels(covered_experience_id) raise ArgumentError, "covered_experience_id is required" if covered_experience_id.nil? definition = Labkit::CoveredExperience::Registry.new[covered_experience_id] definition.to_h.slice(:id, :feature_category, :urgency) end end # Matcher for verifying CoveredExperience start metrics instrumentation. # # Usage: # expect { subject }.to start_covered_experience('rails_request') # # This matcher verifies that the following metric is incremented: # - gitlab_covered_experience_checkpoint_total (with checkpoint=start) # # Parameters: # - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request') RSpec::Matchers.define :start_covered_experience do |covered_experience_id| include CoveredExperienceMetrics description { "start covered experience '#{covered_experience_id}'" } supports_block_expectations match do |actual| labels = base_labels(covered_experience_id) checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i actual.call checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i @checkpoint_change = checkpoint_after - checkpoint_before @checkpoint_change == 1 end failure_message do "Failed to checkpoint covered experience '#{covered_experience_id}':\n" \ "expected checkpoint='start' counter to increase by 1, but increased by #{@checkpoint_change}" end end # Matcher for verifying CoveredExperience checkpoint metrics instrumentation. # # Usage: # expect { subject }.to checkpoint_covered_experience('rails_request') # # This matcher verifies that the following metric is incremented: # - gitlab_covered_experience_checkpoint_total (with checkpoint=intermediate) # # Parameters: # - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request') RSpec::Matchers.define :checkpoint_covered_experience do |covered_experience_id| include CoveredExperienceMetrics description { "checkpoint covered experience '#{covered_experience_id}'" } supports_block_expectations match do |actual| labels = base_labels(covered_experience_id) checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i actual.call checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i @checkpoint_change = checkpoint_after - checkpoint_before @checkpoint_change == 1 end failure_message do "Failed to checkpoint covered experience '#{covered_experience_id}':\n" \ "expected checkpoint='intermediate' counter to increase by 1, but increased by #{@checkpoint_change}" end match_when_negated do |actual| labels = base_labels(covered_experience_id) checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i actual.call checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i @checkpoint_change = checkpoint_after - checkpoint_before @checkpoint_change == 0 end failure_message_when_negated do "Expected covered experience '#{covered_experience_id}' NOT to checkpoint:\n" \ "expected checkpoint='intermediate' counter to increase by 0, but increased by #{@checkpoint_change}" end end # Matcher for verifying CoveredExperience completion metrics instrumentation. # # Usage: # expect { subject }.to covered_experience_completed('rails_request') # # This matcher verifies that the following metrics are incremented with specific labels: # - gitlab_covered_experience_checkpoint_total (with checkpoint=end) # - gitlab_covered_experience_total (with error=false) # - gitlab_covered_experience_apdex_total (with success=true) # # Parameters: # - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request') # - error: Optional. The expected error flag for gitlab_covered_experience_total (false by default) # - error: Optional. The expected success flag for gitlab_covered_experience_apdex_total (true by default) RSpec::Matchers.define :complete_covered_experience do |covered_experience_id, error: false, success: true| include CoveredExperienceMetrics description { "complete covered experience '#{covered_experience_id}'" } supports_block_expectations match do |actual| labels = base_labels(covered_experience_id) checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i total_before = total_counter&.get(labels.merge(error: error)).to_i apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i actual.call checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i total_after = total_counter&.get(labels.merge(error: error)).to_i apdex_after = apdex_counter&.get(labels.merge(success: success)).to_i @checkpoint_change = checkpoint_after - checkpoint_before @total_change = total_after - total_before @apdex_change = apdex_after - apdex_before @checkpoint_change == 1 && @total_change == 1 && @apdex_change == (error ? 0 : 1) end failure_message do "Failed to complete covered experience '#{covered_experience_id}':\n" \ "expected checkpoint='end' counter to increase by 1, but increased by #{@checkpoint_change}\n" \ "expected total='error: #{error}' counter to increase by 1, but increased by #{@total_change}\n" \ "expected apdex='success: #{success}' counter to increase by 1, but increased by #{@apdex_change}" end match_when_negated do |actual| labels = base_labels(covered_experience_id) checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i total_before = total_counter&.get(labels.merge(error: error)).to_i apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i actual.call checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i total_after = total_counter&.get(labels.merge(error: error)).to_i apdex_after = apdex_counter&.get(labels.merge(success: success)).to_i @checkpoint_change = checkpoint_after - checkpoint_before @total_change = total_after - total_before @apdex_change = apdex_after - apdex_before @checkpoint_change == 0 && @total_change == 0 && @apdex_change == (error ? 1 : 0) end failure_message_when_negated do "Failed covered experience '#{covered_experience_id}' NOT to complete:\n" \ "expected checkpoint='end' counter to increase by 0, but increased by #{@checkpoint_change}\n" \ "expected total='error: #{error}' counter to increase by 0, but increased by #{@total_change}\n" \ "expected apdex='success: #{success}' counter to increase by 0, but increased by #{@apdex_change}" end end