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.
- checksums.yaml +4 -4
- data/.copier-answers.yml +2 -1
- data/.gitlab-ci-asdf-versions.yml +3 -3
- data/.gitlab-ci.yml +3 -9
- data/.pre-commit-config.yaml +2 -2
- data/.rubocop_todo.yml +1 -1
- data/.tool-versions +3 -3
- data/README.md +3 -1
- data/config/covered_experiences/schema.json +35 -0
- data/config/covered_experiences/testing_sample.yml +4 -0
- data/gitlab-labkit.gemspec +1 -0
- data/lib/gitlab-labkit.rb +2 -1
- data/lib/labkit/context.rb +1 -0
- data/lib/labkit/covered_experience/README.md +185 -0
- data/lib/labkit/covered_experience/current.rb +34 -0
- data/lib/labkit/covered_experience/error.rb +11 -0
- data/lib/labkit/covered_experience/experience.rb +287 -0
- data/lib/labkit/covered_experience/null.rb +29 -0
- data/lib/labkit/covered_experience/registry.rb +105 -0
- data/lib/labkit/covered_experience.rb +96 -0
- data/lib/labkit/logging/json_logger.rb +11 -0
- data/lib/labkit/middleware/rack.rb +23 -0
- data/lib/labkit/middleware/sidekiq/client.rb +1 -0
- data/lib/labkit/middleware/sidekiq/covered_experience/client.rb +27 -0
- data/lib/labkit/middleware/sidekiq/covered_experience/server.rb +25 -0
- data/lib/labkit/middleware/sidekiq/covered_experience.rb +14 -0
- data/lib/labkit/middleware/sidekiq/server.rb +1 -0
- data/lib/labkit/middleware/sidekiq.rb +1 -0
- data/lib/labkit/rspec/README.md +132 -0
- data/lib/labkit/rspec/matchers/covered_experience_matchers.rb +204 -0
- data/lib/labkit/rspec/matchers.rb +10 -0
- metadata +31 -2
@@ -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.
|
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.
|
557
|
+
rubygems_version: 3.6.9
|
529
558
|
specification_version: 4
|
530
559
|
summary: Instrumentation for GitLab
|
531
560
|
test_files: []
|