rspec_trunk_flaky_tests 0.12.9.pre.beta.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '00089c490aca3f0766ade829dd6e96620cc900a7173fef7851d6844a8ebb7375'
4
+ data.tar.gz: 9a62065dccb28f61dbb2d440d6880a65b6e2174d4ae86634158fff88ab3cd5cb
5
+ SHA512:
6
+ metadata.gz: 561359db55239e235de3929888c2aa779d26afc05862477723c764e4821c9760d19dd44195e87bfa2f771fbb242392224b297944d861f5ccfd814d4a4ad22269
7
+ data.tar.gz: bf4fca81d43e248167cc4c3b4bc8c8495c87083f2c9c43201e9333caa0e48017f8948b2a6a01c02174f2a022b62f2f4372cdf792f8c417e877072e00b60f13ad
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
5
+ require_relative "rspec_trunk_flaky_tests/#{ruby_version}/rspec_trunk_flaky_tests"
6
+ rescue LoadError
7
+ begin
8
+ require_relative 'rspec_trunk_flaky_tests/rspec_trunk_flaky_tests'
9
+ rescue LoadError
10
+ raise LoadError, <<~MSG
11
+ Could not load the native extension for rspec_trunk_flaky_tests.
12
+
13
+ You are using the pure ruby variant of this gem, which is a placeholder
14
+ that exists so that Gemfile.lock files with `PLATFORMS ruby` can resolve
15
+ this dependency. It does not include a precompiled native extension.
16
+
17
+ Precompiled native gems are available for:
18
+ x86_64-linux, aarch64-linux, arm64-darwin, x86_64-darwin
19
+
20
+ If you are on a supported platform and seeing this error, make sure
21
+ bundler is selecting the native variant for your platform:
22
+
23
+ bundle lock --add-platform #{RUBY_PLATFORM}
24
+ bundle install
25
+
26
+ If you are on an unsupported platform, this gem cannot be used in your
27
+ environment. Please open an issue at:
28
+ https://github.com/trunk-io/analytics-cli/issues
29
+
30
+ Current platform: #{RUBY_PLATFORM}
31
+ Current Ruby version: #{RUBY_VERSION}
32
+ MSG
33
+ end
34
+ end
@@ -0,0 +1,260 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Trunk RSpec Helper
4
+ #
5
+ # This helper integrates Trunk Flaky Tests with RSpec to automatically
6
+ # quarantine flaky tests and upload test results.
7
+ #
8
+ # Required environment variables:
9
+ # TRUNK_ORG_URL_SLUG - Your organization's URL slug
10
+ # TRUNK_API_TOKEN - Your API token for authentication
11
+ #
12
+ # Optional environment variables for repository metadata:
13
+ # TRUNK_REPO_ROOT - Path to repository root
14
+ # TRUNK_REPO_URL - Repository URL (e.g., https://github.com/org/repo.git)
15
+ # TRUNK_REPO_HEAD_SHA - HEAD commit SHA
16
+ # TRUNK_REPO_HEAD_BRANCH - HEAD branch name
17
+ # TRUNK_REPO_HEAD_COMMIT_EPOCH - HEAD commit timestamp (seconds since epoch)
18
+ # TRUNK_REPO_HEAD_AUTHOR_NAME - HEAD commit author name
19
+ # TRUNK_PR_NUMBER - PR number, if uploading from a PR (normally inferred from CI environment variables)
20
+ #
21
+ # Optional environment variables for configuration:
22
+ # TRUNK_CODEOWNERS_PATH - Path to CODEOWNERS file
23
+ # TRUNK_VARIANT - Variant name for test results (e.g., 'linux', 'pr-123')
24
+ # TRUNK_DISABLE_QUARANTINING - Set to 'true' to disable quarantining
25
+ # TRUNK_ALLOW_EMPTY_TEST_RESULTS - Set to 'true' to allow empty results
26
+ # TRUNK_DRY_RUN - Set to 'true' to save bundle locally instead of uploading
27
+ # TRUNK_USE_UNCLONED_REPO - Set to 'true' for uncloned repo mode
28
+ # TRUNK_LOCAL_UPLOAD_DIR - Directory to save test results locally (disables upload)
29
+ # TRUNK_QUARANTINED_TESTS_DISK_CACHE_TTL_SECS - Time to cache quarantined tests on disk (in seconds)
30
+ # DISABLE_RSPEC_TRUNK_FLAKY_TESTS - Set to 'true' to completely disable Trunk
31
+ #
32
+ require 'rspec/core'
33
+ require 'time'
34
+ require 'rspec_trunk_flaky_tests'
35
+
36
+ # String is an override to the main String class that is used to colorize the output
37
+ # it is used to make the output more readable
38
+ class String
39
+ def colorize(color_code)
40
+ "\e[#{color_code}m#{self}\e[0m"
41
+ end
42
+
43
+ def red
44
+ colorize(31)
45
+ end
46
+
47
+ def green
48
+ colorize(32)
49
+ end
50
+
51
+ def yellow
52
+ colorize(33)
53
+ end
54
+ end
55
+
56
+ def escape(str)
57
+ str.dump[1..-2]
58
+ end
59
+
60
+ # Knapsack example detector instantiates all test cases in order to determine how to shard them
61
+ # These instantiations should not generate test bundles, so we
62
+ # disable the gem when running under knapsack_pro:rspec_test_example_detector
63
+ def knapsack_detector_mode?
64
+ knapsack_detector_command?
65
+ end
66
+
67
+ def knapsack_detector_command?
68
+ command_line = "#{$PROGRAM_NAME} #{ARGV.join(' ')}".strip
69
+ command_line.include?('knapsack_pro:rspec_test_example_detector') ||
70
+ command_line.include?('knapsack_pro:queue:rspec:initialize')
71
+ end
72
+
73
+ def trunk_disabled
74
+ knapsack_detector_mode? || ENV['DISABLE_RSPEC_TRUNK_FLAKY_TESTS'] == 'true' ||
75
+ ENV['TRUNK_ORG_URL_SLUG'].nil? || ENV['TRUNK_API_TOKEN'].nil?
76
+ end
77
+
78
+ # we want to cache the test report in memory so we can add to it as we go and reduce the number of API calls
79
+ $test_report = TestReport.new('rspec', "#{$PROGRAM_NAME} #{ARGV.join(' ')}", nil)
80
+ $failure_encountered_and_quarantining_disabled = false
81
+
82
+ module RSpec
83
+ module Core
84
+ # Example is the class that represents a test case
85
+ class Example
86
+ # keep the original method around so we can call it
87
+ alias set_exception_core set_exception
88
+ alias assign_generated_description_core assign_generated_description
89
+ # RSpec uses the existance of an exception to determine if the test failed
90
+ # We need to override this to allow us to capture the exception and then
91
+ # decide if we want to fail the test or not
92
+ # trunk-ignore(rubocop/Naming/AccessorMethodName)
93
+ def set_exception(exception)
94
+ return set_exception_core(exception) if metadata[:pending]
95
+ return set_exception_core(exception) if trunk_disabled
96
+ return set_exception_core(exception) if metadata[:retry_attempts]&.positive?
97
+
98
+ handle_quarantine_check(exception)
99
+ end
100
+
101
+ # trunk-ignore(rubocop/Metrics/AbcSize,rubocop/Metrics/MethodLength)
102
+ def handle_quarantine_check(exception)
103
+ id = generate_trunk_id
104
+ name = full_description
105
+ parent_name = example_group.metadata[:description]
106
+ parent_name = parent_name.empty? ? 'rspec' : parent_name
107
+ file = escape(metadata[:file_path])
108
+ classname = file.sub(%r{\.[^/.]+\Z}, '').gsub('/', '.').gsub(/\A\.+|\.+\Z/, '')
109
+ unless $failure_encountered_and_quarantining_disabled
110
+ puts "Test failed, checking if it can be quarantined: `#{location}`".yellow
111
+ end
112
+ is_quarantined_result = $test_report.is_quarantined(id, name, parent_name, classname, file)
113
+ if is_quarantined_result.quarantining_disabled_for_repo
114
+ unless $failure_encountered_and_quarantining_disabled
115
+ puts 'Quarantining is disabled for this repo, no failures will be quarantined'.yellow
116
+ $failure_encountered_and_quarantining_disabled = true
117
+ end
118
+ set_exception_core(exception)
119
+ elsif is_quarantined_result.test_is_quarantined
120
+ # monitor the override in the metadata
121
+ metadata[:quarantined_exception] = exception
122
+ puts "Test is quarantined, overriding exception: #{exception}".green
123
+ nil
124
+ else
125
+ puts 'Test is not quarantined, continuing'.red
126
+ set_exception_core(exception)
127
+ end
128
+ end
129
+
130
+ def assign_generated_description
131
+ metadata[:is_description_generated] = description_generated?
132
+ assign_generated_description_core
133
+ end
134
+
135
+ def description_generated?
136
+ return metadata[:is_description_generated] unless metadata[:is_description_generated].nil?
137
+
138
+ description == location_description
139
+ end
140
+
141
+ def generate_trunk_id
142
+ return "trunk:#{id}-#{location}" if description_generated?
143
+ end
144
+
145
+ # Procsy is a class that is used to wrap execution of the Example class
146
+ class Procsy
147
+ def run_with_trunk
148
+ RSpec::Trunk.new(self).run
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ module RSpec
156
+ # Trunk is a class that is used to monitor the execution of the Example class
157
+ class Trunk
158
+ def self.setup
159
+ RSpec.configure do |config|
160
+ if trunk_disabled
161
+ config.around(:each, &:run)
162
+ else
163
+ config.around(:each, &:run_with_trunk)
164
+ end
165
+ end
166
+ end
167
+
168
+ def initialize(example)
169
+ @example = example
170
+ end
171
+
172
+ def current_example
173
+ @current_example ||= RSpec.current_example
174
+ end
175
+
176
+ def run
177
+ # run the test
178
+ @example.run
179
+ # monitor attempts in the metadata
180
+ if @example.metadata[:attempt_number]
181
+ @example.metadata[:attempt_number] += 1
182
+ else
183
+ @example.metadata[:attempt_number] = 0
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ # TrunkAnalyticsListener is a class that is used to listen to the execution of the Example class
190
+ # it generates and submits the final test reports
191
+ class TrunkAnalyticsListener
192
+ def initialize
193
+ @testreport = $test_report
194
+ end
195
+
196
+ def example_finished(notification)
197
+ add_test_case(notification.example)
198
+ end
199
+
200
+ # trunk-ignore(rubocop/Metrics/MethodLength)
201
+ def close(_notification)
202
+ if $failure_encountered_and_quarantining_disabled
203
+ puts 'Note: Quarantining is disabled for this repo. Test failures were not quarantined.'.yellow
204
+ end
205
+ if ENV['TRUNK_LOCAL_UPLOAD_DIR']
206
+ saved = @testreport.try_save(ENV['TRUNK_LOCAL_UPLOAD_DIR'])
207
+ if saved
208
+ puts 'Local Flaky tests report generated'.green
209
+ else
210
+ puts 'Failed to generate local flaky tests report'.red
211
+ end
212
+ else
213
+ published = @testreport.publish
214
+ if published
215
+ puts 'Flaky tests report upload complete'.green
216
+ else
217
+ puts 'Failed to publish flaky tests report'.red
218
+ end
219
+ end
220
+ end
221
+
222
+ # trunk-ignore(rubocop/Metrics/CyclomaticComplexity,rubocop/Metrics/AbcSize,rubocop/Metrics/MethodLength)
223
+ def add_test_case(example)
224
+ failure_message = example.exception.to_s if example.exception
225
+ failure_message = example.metadata[:quarantined_exception].to_s if example.metadata[:quarantined_exception]
226
+ # TODO: should we use concatenated string or alias when auto-generated description?
227
+ name = example.full_description
228
+ file = escape(example.metadata[:file_path])
229
+ classname = file.sub(%r{\.[^/.]+\Z}, '').gsub('/', '.').gsub(/\A\.+|\.+\Z/, '')
230
+ line = example.metadata[:line_number]
231
+ started_at = example.execution_result.started_at.to_i
232
+ finished_at = example.execution_result.finished_at.to_i
233
+ id = example.generate_trunk_id
234
+
235
+ attempt_number = example.metadata[:retry_attempts] || example.metadata[:attempt_number] || 0
236
+ status = example.execution_result.status.to_s
237
+ # set the status to failure, but mark it as quarantined
238
+ is_quarantined = example.metadata[:quarantined_exception] ? true : false
239
+ case example.execution_result.status
240
+ when :passed
241
+ status = is_quarantined ? Status.new('failure') : Status.new('success')
242
+ when :failed
243
+ status = Status.new('failure')
244
+ when :pending
245
+ status = Status.new('skipped')
246
+ end
247
+ parent_name = example.example_group.metadata[:description]
248
+ parent_name = parent_name.empty? ? 'rspec' : parent_name
249
+ @testreport.add_test(id, name, classname, file, parent_name, line, status, attempt_number,
250
+ started_at, finished_at, failure_message || '', is_quarantined)
251
+ end
252
+ end
253
+
254
+ RSpec.configure do |c|
255
+ next if trunk_disabled
256
+
257
+ c.reporter.register_listener TrunkAnalyticsListener.new, :example_finished, :close
258
+ end
259
+
260
+ RSpec::Trunk.setup
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec_trunk_flaky_tests
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.12.9.pre.beta.0
5
+ platform: ruby
6
+ authors:
7
+ - Trunk Technologies, Inc.
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rspec-core
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ description: Integrates RSpec with Trunk Flaky Tests to automatically upload test
41
+ results from your CI jobs. Enables accurate flaky test detection, quarantining,
42
+ and analytics.
43
+ email: support@trunk.io
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/rspec_trunk_flaky_tests.rb
49
+ - lib/trunk_spec_helper.rb
50
+ homepage: https://docs.trunk.io/flaky-tests/get-started/frameworks/rspec
51
+ licenses:
52
+ - MIT
53
+ metadata:
54
+ source_code_uri: https://github.com/trunk-io/analytics-cli/tree/main/rspec-trunk-flaky-tests
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '3.0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubygems_version: 4.0.6
70
+ specification_version: 4
71
+ summary: RSpec plugin for Trunk Flaky Tests - automatically uploads test results to
72
+ detect and quarantine flaky tests
73
+ test_files: []