gitlab-labkit 0.39.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,132 @@
1
+ # Labkit RSpec Support
2
+
3
+ This module provides RSpec matchers for testing Labkit functionality in your Rails applications.
4
+
5
+ ## Setup
6
+
7
+ You must explicitly require the RSpec matchers in your test files:
8
+
9
+ ```ruby
10
+ # In your spec_helper.rb or rails_helper.rb
11
+ require 'labkit/rspec/matchers'
12
+ ```
13
+
14
+ This approach ensures that:
15
+ - Test dependencies are not loaded in production environments
16
+ - You have explicit control over which matchers are available
17
+ - The gem remains lightweight for non-testing use cases
18
+
19
+
20
+ ## Available Matchers
21
+
22
+ ### Covered Experience Matchers
23
+
24
+ These matchers help you test that your code properly instruments covered experiences with the expected metrics.
25
+
26
+ #### `start_covered_experience`
27
+
28
+ Tests that a covered experience is started (checkpoint=start metric is incremented).
29
+
30
+ ```ruby
31
+ expect { subject }.to start_covered_experience('rails_request')
32
+
33
+ # Test that it does NOT start
34
+ expect { subject }.not_to start_covered_experience('rails_request')
35
+ ```
36
+
37
+ #### `checkpoint_covered_experience`
38
+
39
+ Tests that a covered experience checkpoint is recorded (checkpoint=intermediate metric is incremented).
40
+
41
+ ```ruby
42
+ expect { subject }.to checkpoint_covered_experience('rails_request')
43
+
44
+ # Test that it does NOT checkpoint
45
+ expect { subject }.not_to checkpoint_covered_experience('rails_request')
46
+ ```
47
+
48
+ #### `resume_covered_experience`
49
+
50
+ Tests that a covered experience is resumed (checkpoint=intermediate metric is incremented). This is an alias for `checkpoint_covered_experience` that provides more semantic meaning when testing code that resumes a covered experience previously started.
51
+
52
+ ```ruby
53
+ expect { subject }.to resume_covered_experience('rails_request')
54
+
55
+ # Test that it does NOT resume
56
+ expect { subject }.not_to resume_covered_experience('rails_request')
57
+ ```
58
+
59
+ #### `complete_covered_experience`
60
+
61
+ Tests that a covered experience is completed with the expected metrics:
62
+ - `gitlab_covered_experience_checkpoint_total` (with checkpoint=end)
63
+ - `gitlab_covered_experience_total` (with error flag)
64
+ - `gitlab_covered_experience_apdex_total` (with success flag)
65
+
66
+ ```ruby
67
+ # Test successful completion
68
+ expect { subject }.to complete_covered_experience('rails_request')
69
+
70
+ # Test completion with error
71
+ expect { subject }.to complete_covered_experience('rails_request', error: true, success: false)
72
+
73
+ # Test that it does NOT complete
74
+ expect { subject }.not_to complete_covered_experience('rails_request')
75
+ ```
76
+
77
+ ## Example Usage
78
+
79
+ ### In your spec_helper.rb or rails_helper.rb:
80
+
81
+ ```ruby
82
+ # spec/spec_helper.rb or spec/rails_helper.rb
83
+ require 'gitlab-labkit'
84
+
85
+ # Explicitly require the RSpec matchers
86
+ require 'labkit/rspec/matchers'
87
+
88
+ RSpec.configure do |config|
89
+ # Your other RSpec configuration...
90
+ end
91
+ ```
92
+
93
+ ### In your test files:
94
+
95
+ ```ruby
96
+ RSpec.describe MyController, type: :controller do
97
+ describe '#index' do
98
+ it 'instruments the request properly' do
99
+ expect { get :index }.to start_covered_experience('rails_request')
100
+ .and complete_covered_experience('rails_request')
101
+ end
102
+
103
+ context 'when an error occurs' do
104
+ before do
105
+ allow(MyService).to receive(:call).and_raise(StandardError)
106
+ end
107
+
108
+ it 'records the error in metrics' do
109
+ expect { get :index }.to complete_covered_experience('rails_request', error: true, success: false)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ ```
115
+
116
+ ### For individual spec files (alternative approach):
117
+
118
+ ```ruby
119
+ # spec/controllers/my_controller_spec.rb
120
+ require 'spec_helper'
121
+ require 'labkit/rspec/matchers' # Can also be required per-file if needed
122
+
123
+ RSpec.describe MyController do
124
+ # Your tests using the matchers...
125
+ end
126
+ ```
127
+
128
+ ## Requirements
129
+
130
+ - The covered experience must be registered in `Labkit::CoveredExperience::Registry`
131
+ - Metrics must be properly configured in your test environment
132
+ - The code under test must use Labkit's covered experience instrumentation
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matchers for testing Labkit CoveredExperience functionality
4
+ #
5
+ # This file must be explicitly required in your test setup:
6
+ # require 'labkit/rspec/matchers'
7
+
8
+ raise LoadError, "RSpec is not loaded. Please require 'rspec' before requiring 'labkit/rspec/matchers'" unless defined?(RSpec)
9
+
10
+ module Labkit
11
+ module RSpec
12
+ module Matchers
13
+ # Helper module for CoveredExperience functionality
14
+ module CoveredExperience
15
+ def attributes(covered_experience_id)
16
+ raise ArgumentError, "covered_experience_id is required" if covered_experience_id.nil?
17
+
18
+ definition = Labkit::CoveredExperience::Registry.new[covered_experience_id]
19
+ definition.to_h.slice(:covered_experience, :feature_category, :urgency)
20
+ end
21
+
22
+ def checkpoint_counter
23
+ Labkit::Metrics::Client.get(:gitlab_covered_experience_checkpoint_total)
24
+ end
25
+
26
+ def total_counter
27
+ Labkit::Metrics::Client.get(:gitlab_covered_experience_total)
28
+ end
29
+
30
+ def apdex_counter
31
+ Labkit::Metrics::Client.get(:gitlab_covered_experience_apdex_total)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ # Matcher for verifying CoveredExperience start metrics instrumentation.
39
+ #
40
+ # Usage:
41
+ # expect { subject }.to start_covered_experience('rails_request')
42
+ #
43
+ # This matcher verifies that the following metric is incremented:
44
+ # - gitlab_covered_experience_checkpoint_total (with checkpoint=start)
45
+ #
46
+ # Parameters:
47
+ # - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request')
48
+ RSpec::Matchers.define :start_covered_experience do |covered_experience_id|
49
+ include Labkit::RSpec::Matchers::CoveredExperience
50
+
51
+ description { "start covered experience '#{covered_experience_id}'" }
52
+ supports_block_expectations
53
+
54
+ match do |actual|
55
+ labels = attributes(covered_experience_id)
56
+
57
+ checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i
58
+
59
+ actual.call
60
+
61
+ checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i
62
+
63
+ @checkpoint_change = checkpoint_after - checkpoint_before
64
+
65
+ @checkpoint_change == 1
66
+ end
67
+
68
+ failure_message do
69
+ "Failed to checkpoint covered experience '#{covered_experience_id}':\n" \
70
+ "expected checkpoint='start' counter to increase by 1, but increased by #{@checkpoint_change}"
71
+ end
72
+ end
73
+
74
+ # Matcher for verifying CoveredExperience checkpoint metrics instrumentation.
75
+ #
76
+ # Usage:
77
+ # expect { subject }.to checkpoint_covered_experience('rails_request')
78
+ #
79
+ # This matcher verifies that the following metric is incremented:
80
+ # - gitlab_covered_experience_checkpoint_total (with checkpoint=intermediate)
81
+ #
82
+ # Parameters:
83
+ # - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request')
84
+ RSpec::Matchers.define :checkpoint_covered_experience do |covered_experience_id, by: 1|
85
+ include Labkit::RSpec::Matchers::CoveredExperience
86
+
87
+ description { "checkpoint covered experience '#{covered_experience_id}'" }
88
+ supports_block_expectations
89
+
90
+ match do |actual|
91
+ labels = attributes(covered_experience_id)
92
+
93
+ checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
94
+
95
+ actual.call
96
+
97
+ checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
98
+ @checkpoint_change = checkpoint_after - checkpoint_before
99
+
100
+ # Automatic checkpoints can be created in-between depending on the context,
101
+ # such as pushing experiences to background jobs. For this reason, we check
102
+ # that the value increases by at least "by".
103
+ @checkpoint_change >= by
104
+ end
105
+
106
+ failure_message do
107
+ "Failed to checkpoint covered experience '#{covered_experience_id}':\n" \
108
+ "expected checkpoint='intermediate' counter to increase by at least #{by}, but increased by #{@checkpoint_change}"
109
+ end
110
+
111
+ match_when_negated do |actual|
112
+ labels = attributes(covered_experience_id)
113
+
114
+ checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
115
+
116
+ actual.call
117
+
118
+ checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
119
+ @checkpoint_change = checkpoint_after - checkpoint_before
120
+
121
+ @checkpoint_change.zero?
122
+ end
123
+
124
+ failure_message_when_negated do
125
+ "Expected covered experience '#{covered_experience_id}' NOT to checkpoint:\n" \
126
+ "expected checkpoint='intermediate' counter not to increase, but increased by #{@checkpoint_change}"
127
+ end
128
+ end
129
+
130
+ # Alias for checkpoint_covered_experience matcher
131
+ RSpec::Matchers.alias_matcher :resume_covered_experience, :checkpoint_covered_experience
132
+
133
+ # Matcher for verifying CoveredExperience completion metrics instrumentation.
134
+ #
135
+ # Usage:
136
+ # expect { subject }.to complete_covered_experience('rails_request')
137
+ #
138
+ # This matcher verifies that the following metrics are incremented with specific labels:
139
+ # - gitlab_covered_experience_checkpoint_total (with checkpoint=end)
140
+ # - gitlab_covered_experience_total (with error=false)
141
+ # - gitlab_covered_experience_apdex_total (with success=true)
142
+ #
143
+ # Parameters:
144
+ # - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request')
145
+ # - error: Optional. The expected error flag for gitlab_covered_experience_total (false by default)
146
+ # - success: Optional. The expected success flag for gitlab_covered_experience_apdex_total (true by default)
147
+ RSpec::Matchers.define :complete_covered_experience do |covered_experience_id, error: false, success: true|
148
+ include Labkit::RSpec::Matchers::CoveredExperience
149
+
150
+ description { "complete covered experience '#{covered_experience_id}'" }
151
+ supports_block_expectations
152
+
153
+ match do |actual|
154
+ labels = attributes(covered_experience_id)
155
+
156
+ checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
157
+ total_before = total_counter&.get(labels.merge(error: error)).to_i
158
+ apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i
159
+
160
+ actual.call
161
+
162
+ checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
163
+ total_after = total_counter&.get(labels.merge(error: error)).to_i
164
+ apdex_after = apdex_counter&.get(labels.merge(success: success)).to_i
165
+ @checkpoint_change = checkpoint_after - checkpoint_before
166
+ @total_change = total_after - total_before
167
+ @apdex_change = apdex_after - apdex_before
168
+
169
+ @checkpoint_change == 1 && @total_change == 1 && @apdex_change == (error ? 0 : 1)
170
+ end
171
+
172
+ failure_message do
173
+ "Failed to complete covered experience '#{covered_experience_id}':\n" \
174
+ "expected checkpoint='end' counter to increase by 1, but increased by #{@checkpoint_change}\n" \
175
+ "expected total='error: #{error}' counter to increase by 1, but increased by #{@total_change}\n" \
176
+ "expected apdex='success: #{success}' counter to increase by 1, but increased by #{@apdex_change}"
177
+ end
178
+
179
+ match_when_negated do |actual|
180
+ labels = attributes(covered_experience_id)
181
+
182
+ checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
183
+ total_before = total_counter&.get(labels.merge(error: error)).to_i
184
+ apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i
185
+
186
+ actual.call
187
+
188
+ checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
189
+ total_after = total_counter&.get(labels.merge(error: error)).to_i
190
+ apdex_after = apdex_counter&.get(labels.merge(success: success)).to_i
191
+ @checkpoint_change = checkpoint_after - checkpoint_before
192
+ @total_change = total_after - total_before
193
+ @apdex_change = apdex_after - apdex_before
194
+
195
+ @checkpoint_change.zero? && @total_change.zero? && @apdex_change == (error ? 1 : 0)
196
+ end
197
+
198
+ failure_message_when_negated do
199
+ "Failed covered experience '#{covered_experience_id}' NOT to complete:\n" \
200
+ "expected checkpoint='end' counter to increase by 0, but increased by #{@checkpoint_change}\n" \
201
+ "expected total='error: #{error}' counter to increase by 0, but increased by #{@total_change}\n" \
202
+ "expected apdex='success: #{success}' counter to increase by 0, but increased by #{@apdex_change}"
203
+ end
204
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matchers loader for Labkit
4
+ #
5
+ # This file loads all available RSpec matchers for Labkit.
6
+ # It must be explicitly required in your test setup.
7
+
8
+ raise LoadError, "RSpec is not loaded. Please require 'rspec' before requiring 'labkit/rspec/matchers'" unless defined?(RSpec)
9
+
10
+ require_relative 'matchers/covered_experience_matchers'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-labkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.39.0
4
+ version: 0.41.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Newdigate
@@ -91,6 +91,20 @@ dependencies:
91
91
  - - "~>"
92
92
  - !ruby/object:Gem::Version
93
93
  version: 1.1.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: json-schema
96
+ requirement: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '5.1'
101
+ type: :runtime
102
+ prerelease: false
103
+ version_requirements: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: '5.1'
94
108
  - !ruby/object:Gem::Dependency
95
109
  name: opentracing
96
110
  requirement: !ruby/object:Gem::Requirement
@@ -433,6 +447,8 @@ files:
433
447
  - LICENSE
434
448
  - README.md
435
449
  - Rakefile
450
+ - config/covered_experiences/schema.json
451
+ - config/covered_experiences/testing_sample.yml
436
452
  - gitlab-labkit.gemspec
437
453
  - lib/gitlab-labkit.rb
438
454
  - lib/labkit/context.rb
@@ -442,6 +458,13 @@ files:
442
458
  - lib/labkit/correlation/grpc/client_interceptor.rb
443
459
  - lib/labkit/correlation/grpc/grpc_common.rb
444
460
  - lib/labkit/correlation/grpc/server_interceptor.rb
461
+ - lib/labkit/covered_experience.rb
462
+ - lib/labkit/covered_experience/README.md
463
+ - lib/labkit/covered_experience/current.rb
464
+ - lib/labkit/covered_experience/error.rb
465
+ - lib/labkit/covered_experience/experience.rb
466
+ - lib/labkit/covered_experience/null.rb
467
+ - lib/labkit/covered_experience/registry.rb
445
468
  - lib/labkit/excon_publisher.rb
446
469
  - lib/labkit/fips.rb
447
470
  - lib/labkit/httpclient_publisher.rb
@@ -463,12 +486,18 @@ files:
463
486
  - lib/labkit/middleware/sidekiq/context.rb
464
487
  - lib/labkit/middleware/sidekiq/context/client.rb
465
488
  - lib/labkit/middleware/sidekiq/context/server.rb
489
+ - lib/labkit/middleware/sidekiq/covered_experience.rb
490
+ - lib/labkit/middleware/sidekiq/covered_experience/client.rb
491
+ - lib/labkit/middleware/sidekiq/covered_experience/server.rb
466
492
  - lib/labkit/middleware/sidekiq/server.rb
467
493
  - lib/labkit/middleware/sidekiq/tracing.rb
468
494
  - lib/labkit/middleware/sidekiq/tracing/client.rb
469
495
  - lib/labkit/middleware/sidekiq/tracing/server.rb
470
496
  - lib/labkit/middleware/sidekiq/tracing/sidekiq_common.rb
471
497
  - lib/labkit/net_http_publisher.rb
498
+ - lib/labkit/rspec/README.md
499
+ - lib/labkit/rspec/matchers.rb
500
+ - lib/labkit/rspec/matchers/covered_experience_matchers.rb
472
501
  - lib/labkit/system.rb
473
502
  - lib/labkit/tracing.rb
474
503
  - lib/labkit/tracing/abstract_instrumenter.rb
@@ -525,7 +554,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
525
554
  - !ruby/object:Gem::Version
526
555
  version: '0'
527
556
  requirements: []
528
- rubygems_version: 3.6.7
557
+ rubygems_version: 3.6.9
529
558
  specification_version: 4
530
559
  summary: Instrumentation for GitLab
531
560
  test_files: []