appraisal2 3.0.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.
data/README.md ADDED
@@ -0,0 +1,700 @@
1
+ <p align="center">
2
+ <a href="https://discord.gg/3qme4XHNKN" target="_blank" rel="noopener">
3
+ <img width="120px" src="https://github.com/galtzo-floss/shields-badge/raw/main/docs/images/logo/galtzo-floss-logos-original.svg?raw=true" alt="Galtzo.com Logo by Aboling0, CC BY-SA 4.0">
4
+ </a>
5
+ <a href="https://appraisal2.galtzo.com" target="_blank" rel="noopener">
6
+ <img width="120px" src="https://github.com/appraisal-rb/appraisal2/raw/main/docs/images/logo/LoupeAppraiser.svg?raw=true" alt="appraisal-rb Logo by Aboling0, CC BY-SA 4.0">
7
+ </a>
8
+ <a href="https://www.ruby-lang.org/" target="_blank" rel="noopener">
9
+ <img width="120px" src="https://github.com/galtzo-floss/shields-badge/raw/main/docs/images/logo/ruby-logo-198px.svg?raw=true" alt="Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5">
10
+ </a>
11
+ </p>
12
+
13
+ # 🔍️ Appraisal2
14
+
15
+ > Find out what my gems are worth!
16
+
17
+ - You, possibly
18
+
19
+ [![Version][👽versioni]][👽version] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![Depfu][🔑depfui♻️]][🔑depfu] [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls] [![QLTY Test Coverage][🔑qlty-covi]][🔑qlty-cov] [![QLTY Maintainability][🔑qlty-mnti]][🔑qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf]
20
+
21
+ ---
22
+
23
+ [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
24
+
25
+ ## 🌻 Synopsis
26
+
27
+ Appraisal2 integrates with bundler and rake to test your library against
28
+ different versions of dependencies in repeatable scenarios called "appraisals."
29
+ Appraisal2 is designed to make it easy to check for regressions in your library
30
+ without interfering with day-to-day development using Bundler.
31
+
32
+ Appraisal2 is a hard fork of the venerable appraisal gem,
33
+ which thoughtbot maintained for many years.
34
+ Many thanks to [thoughtbot](https://github.com/thoughtbot/),
35
+ and [Joe Ferris](https://github.com/jferris), the original author!
36
+
37
+ Appraisal2 adds:
38
+
39
+ - support for `eval_gemfile`
40
+ - support for Ruby 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6 (all removed, or planned-to-be, in thoughtbot's `appraisal`)
41
+ - NOTE: The [setup-ruby GH Action](https://github.com/ruby/setup-ruby) only ships support for Ruby 2.3+, so older Rubies are no longer tested in CI. Compatibility is assumed thanks to [![Enforced Code Style Linter][💎rlts-img]][💎rlts] enforcing the syntax for the oldest supported Ruby, which is Ruby v1.8. File a bug if you find something broken.
42
+ - Support for JRuby 9.4+
43
+ - updated and improved documentation
44
+ - many other fixes and improvements. See [CHANGELOG](CHANGELOG.md) for details.
45
+
46
+ ## 💡 Info you can shake a stick at
47
+
48
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
49
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
50
+ | Works with JRuby | [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j9.4-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
51
+ | Works with Truffle Ruby | [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
52
+ | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-r3.0-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎4-r3.1-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎4-r3.2-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎4-r3.3-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
53
+ | Works with MRI Ruby 2 | [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-r2.3-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-r2.4-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-r2.5-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎1-r2.6-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎1-r2.7-wf] |
54
+ | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
55
+ | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![BDFL Blog][🚂bdfl-blog-img]][🚂bdfl-blog] [![Wiki][📜wiki-img]][📜wiki] |
56
+ | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
57
+ | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] |
58
+ | Support | [![Live Chat on Discord][✉️discord-invite-img]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
59
+ | Enterprise Support | [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]<br/>💡Subscribe for support guarantees covering _all_ FLOSS dependencies!<br/>💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]!<br/>💡Tidelift pays maintainers to maintain the software you depend on!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers! |
60
+ | Comrade BDFL 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact BDFL][🚂bdfl-contact-img]][🚂bdfl-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
61
+ | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
62
+
63
+ ## ✨ Installation
64
+
65
+ Install the gem and add to the application's Gemfile by executing:
66
+
67
+ $ bundle add appraisal2
68
+
69
+ If bundler is not being used to manage dependencies, install the gem by executing:
70
+
71
+ $ gem install appraisal2
72
+
73
+ ### In a RubyGem library
74
+
75
+ In your package's `.gemspec`:
76
+
77
+ spec.add_development_dependency "appraisal2"
78
+
79
+ Note that gems must be bundled in the global namespace. Bundling gems to a
80
+ local location or vendoring plugins is not fully supported. If you do not want to
81
+ pollute the global namespace, one alternative is
82
+ [RVM's Gemsets](http://rvm.io/gemsets).
83
+
84
+ ### 🔒 Secure Installation
85
+
86
+ `appraisal2` is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
87
+ [stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with
88
+ by following the instructions below.
89
+
90
+ Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
91
+
92
+ ```console
93
+ gem cert --add <(curl -Ls https://raw.github.com/appraisal-rb/appraisal2/main/certs/pboling.pem)
94
+ ```
95
+
96
+ You only need to do that once. Then proceed to install with:
97
+
98
+ ```console
99
+ gem install appraisal2 -P MediumSecurity
100
+ ```
101
+
102
+ The `MediumSecurity` trust profile will verify signed gems, but allow the installation of unsigned dependencies.
103
+
104
+ This is necessary because not all of `appraisal2`’s dependencies are signed, so we cannot use `HighSecurity`.
105
+
106
+ If you want to up your security game full-time:
107
+
108
+ ```console
109
+ bundle config set --global trust-policy MediumSecurity
110
+ ```
111
+
112
+ NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.
113
+
114
+ ## 🔧 Basic Setup
115
+
116
+ Setting up appraisal2 requires an `Appraisals` file (similar to a `Gemfile`) in
117
+ your project root, named "Appraisals" (note the case), and some slight changes
118
+ to your project's `Rakefile`.
119
+
120
+ An `Appraisals` file consists of several appraisal definitions. An appraisal
121
+ definition is simply a list of gem dependencies. For example, to test with a
122
+ few versions of Rails:
123
+
124
+ appraise "rails-3" do
125
+ gem "rails", "3.2.14"
126
+ end
127
+
128
+ appraise "rails-4" do
129
+ gem "rails", "4.0.0"
130
+ end
131
+
132
+ The dependencies in your `Appraisals` file are combined with dependencies in
133
+ your `Gemfile`, so you don't need to repeat anything that's the same for each
134
+ appraisal. If something is specified in both the Gemfile and an appraisal, the
135
+ version from the appraisal takes precedence.
136
+
137
+ ## ⚒️ Basic Usage
138
+
139
+ Once you've configured the appraisals you want to use, you need to install the
140
+ dependencies for each appraisal:
141
+
142
+ $ bundle exec appraisal install
143
+
144
+ This will resolve, install, and lock the dependencies for that appraisal using
145
+ bundler. Once you have your dependencies set up, you can run any command in a
146
+ single appraisal:
147
+
148
+ $ bundle exec appraisal rails-3 rake test
149
+
150
+ This will run `rake test` using the dependencies configured for Rails 3. You can
151
+ also run each appraisal in turn:
152
+
153
+ $ bundle exec appraisal rake test
154
+
155
+ If you want to use only the dependencies from your Gemfile, just run `rake
156
+ test` as normal. This allows you to keep running with the latest versions of
157
+ your dependencies in quick test runs, but keep running the tests in older
158
+ versions to check for regressions.
159
+
160
+ In the case that you want to run all the appraisals by default when you run
161
+ `rake`, you can override your default Rake task by put this into your Rakefile:
162
+
163
+ if !ENV["APPRAISAL_INITIALIZED"] && ENV.fetch("CI", "false").casecmp("false") == 0
164
+ task :default => :appraisal
165
+ end
166
+
167
+ (Appraisal2 sets `APPRAISAL_INITIALIZED` environment variable when it runs your
168
+ process. We put a check here to ensure that `appraisal rake` command should run
169
+ your real default task, which usually is your `test` task.)
170
+
171
+ Note that this may conflict with your CI setup if you decide to split the test
172
+ into multiple processes by Appraisal2 and you are using `rake` to run tests by
173
+ default.
174
+
175
+ ### Commands
176
+
177
+ ```bash
178
+ appraisal clean # Remove all generated gemfiles and lockfiles from gemfiles folder
179
+ appraisal generate # Generate a gemfile for each appraisal
180
+ appraisal help [COMMAND] # Describe available commands or one specific command
181
+ appraisal install # Resolve and install dependencies for each appraisal
182
+ appraisal list # List the names of the defined appraisals
183
+ appraisal update [LIST_OF_GEMS] # Remove all generated gemfiles and lockfiles, resolve, and install dependencies again
184
+ appraisal version # Display the version and exit
185
+ ```
186
+
187
+ Under the hood
188
+ --------------
189
+
190
+ Running `appraisal install` generates a Gemfile for each appraisal by combining
191
+ your root Gemfile with the specific requirements for each appraisal. These are
192
+ stored in the `gemfiles` directory, and should be added to version control to
193
+ ensure that versions within constraints of the Gemfile are always used.
194
+
195
+ When you prefix a command with `appraisal`, the command is run with the
196
+ appropriate Gemfile for that appraisal, ensuring the correct dependencies
197
+ are used.
198
+
199
+ Sharing Modular Gemfiles between Appraisals
200
+ -------
201
+
202
+ _New in appraisal2_ (not possible in thoughtbot's appraisal)
203
+
204
+ It is common for Appraisals to duplicate sets of gems, and sometimes it
205
+ makes sense to DRY this up into a shared, modular, gemfile.
206
+ In a scenario where you do not load your main Gemfile in your Appraisals,
207
+ but you want to declare your various gem sets for e.g.
208
+ `%w(coverage test documentation audit)` once each, you can re-use the same
209
+ modular gemfiles for local development by referencing them from the main
210
+ Gemfile.
211
+
212
+ To do this, use the `eval_gemfile` declaration within the necessary
213
+ `appraise` block in your `Appraisals` file, which will behave the same as
214
+ `eval_gemfile` does in a normal Gemfile.
215
+
216
+ ### Example Usage
217
+
218
+ You could put your modular gemfiles in the `gemfiles` directory, or nest
219
+ them in `gemfiles/modular/*`, which will be used for this example.
220
+
221
+ **Gemfile**
222
+ ```ruby
223
+ eval_gemfile "gemfiles/modular/audit.gemfile"
224
+ ```
225
+
226
+ **gemfiles/modular/audit.gemfile**
227
+ ```ruby
228
+ # Many gems are dropping support for Ruby < 3.1,
229
+ # so we only want to run our security audit in CI on Ruby 3.1+
230
+ gem "bundler-audit", "~> 0.9.2"
231
+ # And other security audit gems...
232
+ ```
233
+
234
+ **Appraisals**
235
+ ```ruby
236
+ appraise "ruby-2-7" do
237
+ gem "dummy"
238
+ end
239
+
240
+ appraise "ruby-3-0" do
241
+ gem "dummy"
242
+ end
243
+
244
+ appraise "ruby-3-1" do
245
+ gem "dummy"
246
+ eval_gemfile "modular/audit.gemfile"
247
+ end
248
+
249
+ appraise "ruby-3-2" do
250
+ gem "dummy"
251
+ eval_gemfile "modular/audit.gemfile"
252
+ end
253
+
254
+ appraise "ruby-3-3" do
255
+ gem "dummy"
256
+ eval_gemfile "modular/audit.gemfile"
257
+ end
258
+
259
+ appraise "ruby-3-4" do
260
+ gem "dummy"
261
+ eval_gemfile "modular/audit.gemfile"
262
+ end
263
+ ```
264
+
265
+ **Appraisal2.root.gemfile**
266
+ ```ruby
267
+ source "https://rubygems.org"
268
+
269
+ # Appraisal2 Root Gemfile is for running appraisal to generate the Appraisal2 Gemfiles
270
+ # We do not load the standard Gemfile, as it is tailored for local development,
271
+ # while appraisals are tailored for CI.
272
+
273
+ gemspec
274
+
275
+ gem "appraisal2"
276
+ ```
277
+
278
+ Now when you need to update your appraisals:
279
+ ```shell
280
+ BUNDLE_GEMFILE=Appraisal2.root.gemfile bundle exec appraisal update
281
+ ```
282
+
283
+ ### Removing Gems using Appraisal2
284
+
285
+ It is common while managing multiple Gemfiles for dependencies to become deprecated and no
286
+ longer necessary, meaning they need to be removed from the Gemfile for a specific `appraisal`.
287
+ To do this, use the `remove_gem` declaration within the necessary `appraise` block in your
288
+ `Appraisals` file.
289
+
290
+ #### Example Usage
291
+
292
+ **Gemfile**
293
+ ```ruby
294
+ gem "rails", "~> 4.2"
295
+
296
+ group :test do
297
+ gem "rspec", "~> 4.0"
298
+ gem "test_after_commit"
299
+ end
300
+ ```
301
+
302
+ **Appraisals**
303
+ ```ruby
304
+ appraise "rails-5" do
305
+ gem "rails", "~> 5.2"
306
+
307
+ group :test do
308
+ remove_gem "test_after_commit"
309
+ end
310
+ end
311
+ ```
312
+
313
+ Using the `Appraisals` file defined above, this is what the resulting `Gemfile` will look like:
314
+ ```ruby
315
+ gem "rails", "~> 5.2"
316
+
317
+ group :test do
318
+ gem "rspec", "~> 4.0"
319
+ end
320
+ ```
321
+
322
+ ### Customization
323
+
324
+ It is possible to customize the generated Gemfiles by adding a `customize_gemfiles` block to
325
+ your `Appraisals` file. The block must contain a hash of key/value pairs. Currently supported
326
+ customizations include:
327
+ - heading: a string that by default adds "# This file was generated by Appraisal2" to the top of each Gemfile, (the string will be commented for you)
328
+ - single_quotes: a boolean that controls if strings are single quoted in each Gemfile, defaults to false
329
+
330
+ You can also provide variables for substitution in the heading, based on each appraisal. Currently supported variables:
331
+ - `%{appraisal}`: Becomes the name of each appraisal, e.g. `rails-3`
332
+ - `%{gemfile}`: Becomes the filename of each gemfile, e.g. `rails-3.gemfile`
333
+ - `%{gemfile_path}`: Becomes the full path of each gemfile, e.g. `/path/to/project/gemfiles/rails-3.gemfile`
334
+ - `%{lockfile}`: Becomes the filename of each lockfile, e.g. `rails-3.gemfile.lock`
335
+ - `%{lockfile_path}`: Becomes the full path of each lockfile, e.g. `/path/to/project/gemfiles/rails-3.gemfile.lock`
336
+ - `%{relative_gemfile_path}`: Becomes the relative path of each gemfile, e.g. `gemfiles/rails-3.gemfile`
337
+ - `%{relative_lockfile_path}`: Becomes the relative path of each lockfile, e.g. `gemfiles/rails-3.gemfile.lock`
338
+
339
+ #### Example Usage
340
+
341
+ **Appraisals**
342
+ ```ruby
343
+ customize_gemfiles do
344
+ {
345
+ :single_quotes => true,
346
+ :heading => <<-HEADING,
347
+ frozen_string_literal: true
348
+
349
+ `%{gemfile}` has been generated by Appraisal2, do NOT modify it or `%{lockfile}` directly!
350
+ Make the changes to the "%{appraisal}" block in `Appraisals` instead. See the conventions at https://example.com/
351
+ HEADING
352
+ }
353
+ end
354
+
355
+ appraise "rails-3" do
356
+ gem "rails", "3.2.14"
357
+ end
358
+ ```
359
+
360
+ Using the `Appraisals` file defined above, this is what the resulting `Gemfile` will look like:
361
+ ```ruby
362
+ # frozen_string_literal: true
363
+
364
+ # `rails-3.gemfile` has been generated by Appraisal2, do NOT modify it or `rails-3.gemfile.lock` directly!
365
+ # Make the changes to the "rails-3" block in `Appraisals` instead. See the conventions at https://example.com/
366
+
367
+ gem "rails", "3.2.14"
368
+ ```
369
+
370
+ ### Version Control
371
+
372
+ When using Appraisal2, we recommend you check in the Gemfiles that Appraisal2
373
+ generates within the gemfiles directory, but exclude the lockfiles there
374
+ (`*.gemfile.lock`). The Gemfiles are useful when running your tests against a
375
+ continuous integration server.
376
+
377
+ Additionally, the Bundler team [officially recommends](https://github.com/rubygems/bundler-site/pull/501)
378
+ committing the main `Gemfile.lock` for **both** gems **and** libraries.
379
+
380
+ ### Circle CI Integration
381
+
382
+ In Circle CI you can override the default testing behavior.
383
+ You can configure Appraisal2 to execute your tests.
384
+
385
+ In order to this you can put the following configuration in your circle.yml file:
386
+
387
+ ```yml
388
+ dependencies:
389
+ post:
390
+ - bundle exec appraisal install
391
+ test:
392
+ pre:
393
+ - bundle exec appraisal rake db:create
394
+ - bundle exec appraisal rake db:migrate
395
+ override:
396
+ - bundle exec appraisal rspec
397
+ ```
398
+
399
+ Notice that we are running an rspec suite. You can customize your testing
400
+ command in the `override` section and use your favourite one.
401
+
402
+ ## 🔐 Security
403
+
404
+ See [SECURITY.md][🔐security].
405
+
406
+ ## 🤝 Contributing
407
+
408
+ If you need some ideas of where to help, you could work on adding more code coverage,
409
+ or if it is already 💯 (see [below](#code-coverage)) check [issues][🤝gh-issues], or [PRs][🤝gh-pulls],
410
+ or use the gem and think about how it could be better.
411
+
412
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
413
+
414
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
415
+
416
+ ### 🚀 Release Instructions
417
+
418
+ See [CONTRIBUTING.md][🤝contributing].
419
+
420
+ ### Code Coverage
421
+
422
+ [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls]
423
+
424
+ ### 🪇 Code of Conduct
425
+
426
+ Everyone interacting with this project's codebases, issue trackers,
427
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
428
+
429
+ ## 🌈 Contributors
430
+
431
+ [![Contributors][🖐contributors-img]][🖐contributors]
432
+
433
+ Made with [contributors-img][🖐contrib-rocks].
434
+
435
+ ## ⭐️ Star History
436
+
437
+ <a href="https://star-history.com/#appraisal-rb/appraisal2&Date">
438
+ <picture>
439
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=appraisal-rb/appraisal2&type=Date&theme=dark" />
440
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=appraisal-rb/appraisal2&type=Date" />
441
+ <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=appraisal-rb/appraisal2&type=Date" />
442
+ </picture>
443
+ </a>
444
+
445
+ ## 📌 Versioning
446
+
447
+ This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver].
448
+ Violations of this scheme should be reported as bugs.
449
+ Specifically, if a minor or patch version is released that breaks backward compatibility,
450
+ a new version should be immediately released that restores compatibility.
451
+ Breaking changes to the public API will only be introduced with new major versions.
452
+
453
+ ### 📌 Is "Platform Support" part of the public API?
454
+
455
+ Yes. But I'm obligated to include notes...
456
+
457
+ SemVer should, but doesn't explicitly, say that dropping support for specific Platforms
458
+ is a *breaking change* to an API.
459
+ It is obvious to many, but not all, and since the spec is silent, the bike shedding is endless.
460
+
461
+ > dropping support for a platform is both obviously and objectively a breaking change
462
+
463
+ - Jordan Harband (@ljharb, maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
464
+
465
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
466
+ read this article from the creator of SemVer:
467
+
468
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
469
+
470
+ As a result of this policy, and the interpretive lens used by the maintainer,
471
+ you can (and should) specify a dependency on these libraries using
472
+ the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
473
+
474
+ For example:
475
+
476
+ ```ruby
477
+ spec.add_dependency("appraisal2", "~> 3.0")
478
+ ```
479
+
480
+ See [CHANGELOG.md][📌changelog] for a list of releases.
481
+
482
+ ## 📄 License
483
+
484
+ The gem is available as open source under the terms of
485
+ the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref].
486
+ See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer].
487
+
488
+ ### © Copyright
489
+
490
+ <ul>
491
+ <li>
492
+ Copyright (c) 2024-2025 Peter H. Boling, of
493
+ <a href="https://discord.gg/3qme4XHNKN">
494
+ Galtzo.com
495
+ <picture>
496
+ <img src="https://github.com/galtzo-floss/shields-badge/raw/main/docs/images/logo/galtzo-floss-logos-wordless.svg?raw=true" alt="Galtzo.com Logo by Aboling0, CC BY-SA 4.0" width="24">
497
+ </picture>
498
+ </a>, and Appraisal2 contributors
499
+ </li>
500
+ <li>Copyright (c) 2010-2013 Joe Ferris and thoughtbot, inc.</li>
501
+ </ul>
502
+
503
+ ## 🤑 One more thing
504
+
505
+ Having arrived at the bottom of the page, please endure a final supplication.
506
+ The primary maintainer of this gem, Peter Boling, wants
507
+ Ruby to be a great place for people to solve problems, big and small.
508
+ Please consider supporting his efforts via the giant yellow link below,
509
+ or one of smaller ones, depending on button size preference.
510
+
511
+ [![Buy me a latte][🖇buyme-img]][🖇buyme]
512
+
513
+ [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
514
+
515
+ P.S. If you need help️, or want to say thanks, 👇 Join the Discord.
516
+
517
+ [![Live Chat on Discord][✉️discord-invite-img]][✉️discord-invite]
518
+
519
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay
520
+ [⛳liberapay]: https://liberapay.com/pboling/donate
521
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
522
+ [🖇sponsor]: https://github.com/sponsors/pboling
523
+ [🖇polar-img]: https://img.shields.io/badge/polar-donate-yellow.svg
524
+ [🖇polar]: https://polar.sh/pboling
525
+ [🖇kofi-img]: https://img.shields.io/badge/a_more_different_coffee-✓-yellow.svg
526
+ [🖇kofi]: https://ko-fi.com/O5O86SNP4
527
+ [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-yellow.svg
528
+ [🖇patreon]: https://patreon.com/galtzo
529
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-✓-yellow.svg?style=flat
530
+ [🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff
531
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
532
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
533
+ [✉️discord-invite-img]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge
534
+
535
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
536
+ [⛳️gem-namespace]: https://github.com/appraisal-rb/appraisal2
537
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-Appraisal-brightgreen.svg?style=flat&logo=ruby&logoColor=white
538
+ [⛳️gem-name]: https://rubygems.org/gems/appraisal2
539
+ [⛳️name-img]: https://img.shields.io/badge/name-appraisal2-brightgreen.svg?style=flat&logo=rubygems&logoColor=red
540
+ [🚂bdfl-blog]: http://www.railsbling.com/tags/appraisal2
541
+ [🚂bdfl-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
542
+ [🚂bdfl-contact]: http://www.railsbling.com/contact
543
+ [🚂bdfl-contact-img]: https://img.shields.io/badge/Contact-BDFL-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
544
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
545
+ [💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling
546
+ [💖✌️wellfound]: https://angel.co/u/peter-boling
547
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
548
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
549
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
550
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
551
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https%3A%2F%2Fruby.social&style=flat&logo=mastodon&label=Ruby%20%40galtzo
552
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
553
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
554
+ [💖🌳linktree]: https://linktr.ee/galtzo
555
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
556
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
557
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
558
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
559
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
560
+ [💖🧊berg]: https://codeberg.org/pboling
561
+ [💖🐙hub]: https://github.org/pboling
562
+ [💖🛖hut]: https://sr.ht/~galtzo/
563
+ [💖🧪lab]: https://gitlab.com/pboling
564
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
565
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
566
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
567
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
568
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription
569
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
570
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
571
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
572
+ [🚂railsbling]: http://www.railsbling.com
573
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
574
+ [📜src-gl]: https://gitlab.com/appraisal-rb/appraisal2/
575
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
576
+ [📜src-cb]: https://codeberg.org/appraisal-rb/appraisal2
577
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green
578
+ [📜src-gh]: https://github.com/appraisal-rb/appraisal2
579
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
580
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
581
+ [📜wiki]: https://gitlab.com/appraisal-rb/appraisal2/-/wikis/home
582
+ [📜wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=Wiki&logoColor=white
583
+ [👽dl-rank]: https://rubygems.org/gems/appraisal2
584
+ [👽dl-ranki]: https://img.shields.io/gem/rd/appraisal2.svg
585
+ [👽oss-help]: https://www.codetriage.com/appraisal-rb/appraisal2
586
+ [👽oss-helpi]: https://www.codetriage.com/appraisal-rb/appraisal2/badges/users.svg
587
+ [👽version]: https://rubygems.org/gems/appraisal2
588
+ [👽versioni]: https://img.shields.io/gem/v/appraisal2.svg
589
+ [🔑qlty-mnt]: https://qlty.sh/gh/appraisal-rb/projects/appraisal2
590
+ [🔑qlty-mnti]: https://qlty.sh/gh/appraisal-rb/projects/appraisal2/maintainability.svg
591
+ [🔑qlty-cov]: https://qlty.sh/gh/appraisal-rb/projects/appraisal2/metrics/code?sort=coverageRating
592
+ [🔑qlty-covi]: https://qlty.sh/gh/appraisal-rb/projects/appraisal2/coverage.svg
593
+ [🔑codecov]: https://codecov.io/gh/appraisal-rb/appraisal2
594
+ [🔑codecovi♻️]: https://codecov.io/gh/appraisal-rb/appraisal2/branch/main/graph/badge.svg?token=0X5VEW9USD
595
+ [🔑coveralls]: https://coveralls.io/github/appraisal-rb/appraisal2?branch=main
596
+ [🔑coveralls-img]: https://coveralls.io/repos/github/appraisal-rb/appraisal2/badge.svg?branch=main
597
+ [🔑depfu]: https://depfu.com/github/appraisal-rb/appraisal2?project_id=67033
598
+ [🔑depfui♻️]: https://badges.depfu.com/badges/b5344eec8b60c9e72bd9145ff53cd07b/count.svg
599
+ [🖐codeQL]: https://github.com/appraisal-rb/appraisal2/security/code-scanning
600
+ [🖐codeQL-img]: https://github.com/appraisal-rb/appraisal2/actions/workflows/codeql-analysis.yml/badge.svg
601
+ [🚎1-r2.3-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-2-3.yml
602
+ [🚎1-r2.3-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-2-3.yml/badge.svg
603
+ [🚎1-r2.4-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-2-4.yml
604
+ [🚎1-r2.4-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-2-4.yml/badge.svg
605
+ [🚎1-r2.5-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-2-5.yml
606
+ [🚎1-r2.5-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-2-5.yml/badge.svg
607
+ [🚎1-r2.6-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-2-6.yml
608
+ [🚎1-r2.6-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-2-6.yml/badge.svg
609
+ [🚎1-r2.7-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-2-7.yml
610
+ [🚎1-r2.7-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-2-7.yml/badge.svg
611
+ [🚎2-cov-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/coverage.yml
612
+ [🚎2-cov-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/coverage.yml/badge.svg
613
+ [🚎3-hd-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/heads.yml
614
+ [🚎3-hd-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/heads.yml/badge.svg
615
+ [🚎4-r3.0-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-3-0.yml
616
+ [🚎4-r3.0-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-3-0.yml/badge.svg
617
+ [🚎4-r3.1-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-3-1.yml
618
+ [🚎4-r3.1-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-3-1.yml/badge.svg
619
+ [🚎4-r3.2-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-3-2.yml
620
+ [🚎4-r3.2-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-3-2.yml/badge.svg
621
+ [🚎4-r3.3-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-3-3.yml
622
+ [🚎4-r3.3-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/ruby-3-3.yml/badge.svg
623
+ [🚎5-st-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/style.yml
624
+ [🚎5-st-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/style.yml/badge.svg
625
+ [🚎10-j9.4-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/jruby-9-4.yml
626
+ [🚎10-j9.4-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/jruby-9-4.yml/badge.svg
627
+ [🚎11-c-wf]: https://github.com/appraisal-rb/appraisal2/actions/workflows/current.yml
628
+ [🚎11-c-wfi]: https://github.com/appraisal-rb/appraisal2/actions/workflows/current.yml/badge.svg
629
+ [💎ruby-2.3i]: https://img.shields.io/badge/Ruby-2.3-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
630
+ [💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
631
+ [💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
632
+ [💎ruby-2.6i]: https://img.shields.io/badge/Ruby-2.6-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
633
+ [💎ruby-2.7i]: https://img.shields.io/badge/Ruby-2.7-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
634
+ [💎ruby-3.0i]: https://img.shields.io/badge/Ruby-3.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white
635
+ [💎ruby-3.1i]: https://img.shields.io/badge/Ruby-3.1-CC342D?style=for-the-badge&logo=ruby&logoColor=white
636
+ [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
637
+ [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
638
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
639
+ [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
640
+ [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
641
+ [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
642
+ [💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red
643
+ [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
644
+ [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
645
+ [🤝gh-issues]: https://github.com/appraisal-rb/appraisal2/issues
646
+ [🤝gh-pulls]: https://github.com/appraisal-rb/appraisal2/pulls
647
+ [🤝gl-issues]: https://gitlab.com/appraisal-rb/appraisal2/-/issues
648
+ [🤝gl-pulls]: https://gitlab.com/appraisal-rb/appraisal2/-/merge_requests
649
+ [🤝cb-issues]: https://codeberg.org/appraisal-rb/appraisal2/issues
650
+ [🤝cb-pulls]: https://codeberg.org/appraisal-rb/appraisal2/pulls
651
+ [🤝cb-donate]: https://donate.codeberg.org/
652
+ [🤝contributing]: CONTRIBUTING.md
653
+ [🔑codecov-g♻️]: https://codecov.io/gh/appraisal-rb/appraisal2/graphs/tree.svg?token=0X5VEW9USD
654
+ [🖐contrib-rocks]: https://contrib.rocks
655
+ [🖐contributors]: https://github.com/appraisal-rb/appraisal2/graphs/contributors
656
+ [🖐contributors-img]: https://contrib.rocks/image?repo=appraisal-rb/appraisal2
657
+ [🚎contributors-gl]: https://gitlab.com/appraisal-rb/appraisal2/-/graphs/main
658
+ [🪇conduct]: CODE_OF_CONDUCT.md
659
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
660
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
661
+ [📌semver]: https://semver.org/spec/v2.0.0.html
662
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
663
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
664
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
665
+ [📌changelog]: CHANGELOG.md
666
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
667
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
668
+ [📌gitmoji]:https://gitmoji.dev
669
+ [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20😜%20😍-34495e.svg?style=flat-square
670
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
671
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-1.186-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
672
+ [🔐security]: SECURITY.md
673
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
674
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
675
+ [📄license]: LICENSE.txt
676
+ [📄license-ref]: https://opensource.org/licenses/MIT
677
+ [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
678
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
679
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
680
+ [🚎yard-current]: http://rubydoc.info/gems/appraisal2
681
+ [🚎yard-head]: https://appraisal2.galtzo.com
682
+ [💎stone_checksums]: https://github.com/pboling/stone_checksums
683
+ [💎SHA_checksums]: https://gitlab.com/appraisal-rb/appraisal2/-/tree/main/checksums
684
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
685
+ [💎rlts-img]: https://img.shields.io/badge/code_style_%26_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
686
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
687
+
688
+ <details>
689
+ <summary>
690
+ Disabled Badges
691
+ </summary>
692
+
693
+ Badges for failing services.
694
+ Bug reports filed.
695
+ Once fixed, these should look much nicer.
696
+
697
+ [![CodeCov Test Coverage][🔑codecovi♻️]][🔑codecov]
698
+ [![Coverage Graph][🔑codecov-g♻️]][🔑codecov]
699
+
700
+ </details>