rspec_junit_formatter 0.7.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a808950ca0ea8273d57ec14b76e0f33326f2cf473708c8809ce7f80d5daef1f
4
- data.tar.gz: 724eb19bbed6395719c2791f5661a80f5ece271bf85eb44a1d864a883f06edde
3
+ metadata.gz: 86651a64a7f810497cc1958b49c285df2aea4d912e3ad9d2cc59a791902fef4c
4
+ data.tar.gz: e90928c8f44a31a0dfe16344200aabede4e9b11af848de063f4786ba05feb64f
5
5
  SHA512:
6
- metadata.gz: 9bf5b02431be19674c48a308132008bdd93d1619a1efadd197009cd67c150984cdefc918f73209535a936f53dd3847b677c9f1c4823a181cc4e18e5f33411c27
7
- data.tar.gz: db0d242d4eaf205bc38c795f2313a29306d8bdc82218940058929d215a5c7650a22406406bcdd9a585b0793cfa862f62302716f21d498270116625e991b0e871
6
+ metadata.gz: c3620be8b07588d2acd5feaf5e9f795b1c4096dbf029875518084709dc70c057c1abf5c0deaed9beec954d0ff41dc11a4c36bfea404a735648bed11bde79c458
7
+ data.tar.gz: 869977b6c8a97bbf1f2ba08706249463e80260e92c84fd72f4feb0a45ae882214a29430ad4f344cfb7ed5b23373a540ee99ce4729ae7e2ad3dcf01e6cdba9d3c
data/README.md CHANGED
@@ -11,24 +11,10 @@
11
11
  [circleci-junit]: https://circleci.com/docs/2.0/collect-test-data/
12
12
  [gitlab-junit]: https://docs.gitlab.com/ee/ci/unit_test_reports.html#ruby-example
13
13
 
14
- ## Supported versions and legacy upgrades
15
-
16
- **0.7.0 requires Ruby >= 3.3 and RSpec >= 3.13, < 4.** We support upstream-maintained stable Ruby branches, including security maintenance, and the maintained stable RSpec minor series. As of October 2026, [Ruby 3.3 is security-maintained and Ruby 3.4/4.0 are normally maintained](https://www.ruby-lang.org/en/downloads/branches/). [RSpec supports only the latest major/minor pair](https://rspec.info/blog/2024/11/introducing-the-monorepo/), currently 3.13; RSpec 4 prereleases are not supported. Future minor releases may raise these minimums when upstream maintenance ends.
17
-
18
- **[0.6.2](https://rubygems.org/gems/rspec_junit_formatter/versions/0.6.2) is the final legacy-compatible release.** It retains Ruby >= 2.3 and RSpec >= 2, < 4 (except 2.12.0), including the encoding and aggregate failure fixes. See its [compatibility table](https://github.com/sj26/rspec_junit_formatter/blob/v0.6.2/README.md#tested-versus-permitted-versions) for tested combinations and limitations. It does not make end-of-life Ruby or RSpec versions maintained again.
19
-
20
- If you must keep an older Ruby **or** an older RSpec, use:
21
-
22
- ```ruby
23
- gem "rspec_junit_formatter", "0.6.2", require: false
24
- ```
25
-
26
- Then run `bundle update rspec_junit_formatter`. Without Bundler, use `gem install rspec_junit_formatter -v 0.6.2`. Upgrading Ruby alone is not enough if your project must keep an older RSpec.
27
-
28
- To use 0.7.0, upgrade Ruby to a maintained version and RSpec to 3.13, remove the legacy formatter pin, and run `bundle update rspec rspec_junit_formatter`. Both Ruby and `rspec-core` requirements are recorded in the package so dependency resolution can retain 0.6.2 for older environments.
29
-
30
14
  ## Usage
31
15
 
16
+ Requires Ruby >= 3.3 and RSpec >= 3.13, < 4. For older versions, see [legacy support](#supported-versions-and-legacy-upgrades).
17
+
32
18
  Install the gem:
33
19
 
34
20
  ```sh
@@ -80,7 +66,47 @@ For use with `parallel_tests`, add `$TEST_ENV_NUMBER` in the output file option
80
66
  --out tmp/rspec<%= ENV["TEST_ENV_NUMBER"] %>.xml
81
67
  ```
82
68
 
83
- The formatter includes `$TEST_ENV_NUMBER` in the test suite name within the XML, too.
69
+ The formatter appends `$TEST_ENV_NUMBER` to the test suite name within the XML, too.
70
+ An explicitly set value always wins, including the empty string that `parallel_tests`
71
+ uses for its first worker: `TEST_ENV_NUMBER=""` keeps the name `rspec`.
72
+
73
+ When `TEST_ENV_NUMBER` is **unset**, the formatter uses the first non-empty value
74
+ from this table, in order:
75
+
76
+ | Variable | Provider | Native first index |
77
+ | --- | --- | --- |
78
+ | `CI_NODE_INDEX` | [GitLab](https://docs.gitlab.com/ci/variables/predefined_variables/), or a user-supplied CI label | `1` on GitLab |
79
+ | `CIRCLE_NODE_INDEX` | [CircleCI](https://circleci.com/docs/reference/variables/#built-in-environment-variables) | `0` |
80
+ | `SEMAPHORE_JOB_INDEX` | [Semaphore](https://docs.semaphore.io/reference/env-vars#job-index) | `1` |
81
+ | `BUILDKITE_PARALLEL_JOB` | [Buildkite](https://buildkite.com/docs/pipelines/configure/environment-variables#BUILDKITE_PARALLEL_JOB) | `0` |
82
+ | `BITBUCKET_PARALLEL_STEP` | [Bitbucket Pipelines](https://support.atlassian.com/bitbucket-cloud/docs/parallel-step-options/) | `0` |
83
+
84
+ Values are labels, not test-sharding offsets: they are XML-escaped but otherwise
85
+ unchanged. For example, GitLab's `CI_NODE_INDEX=1` produces `rspec1`, while
86
+ Buildkite's `BUILDKITE_PARALLEL_JOB=0` produces `rspec0`. There is no index
87
+ conversion based on `GITLAB_CI`, and a custom `CI_NODE_INDEX` takes precedence over
88
+ the other CI variables. If all these values are empty or unset, the name stays
89
+ `rspec`; no default `0` is appended.
90
+
91
+ The old `SEMAPHORE_CURRENT_THREAD` and `SNAP_WORKER_INDEX` variables are not
92
+ detected: [Semaphore Classic was retired in 2023](https://semaphore.io/blog/semaphore-classic-deprecation)
93
+ and [Snap CI in 2017](https://github.com/snap-ci/snap-ci-blog-content/blob/master/posts/2017-02-06-2017-02-06-snap-announcement.md).
94
+ Other runners can set `TEST_ENV_NUMBER` explicitly.
95
+
96
+ This changes **only the XML suite name**, not the output filename. Continue to
97
+ choose distinct `--out` paths for processes sharing a filesystem. CI indexes are
98
+ unique only within their parallel job group, not across steps, builds, or nested
99
+ `parallel_tests` workers. To combine those reports, set an explicit label that
100
+ includes each relevant dimension, and use it in the filename too. For example,
101
+ inside each `parallel_tests` worker on Buildkite:
102
+
103
+ ```sh
104
+ export TEST_ENV_NUMBER="-${BUILDKITE_STEP_KEY}-${BUILDKITE_PARALLEL_JOB}-${TEST_ENV_NUMBER}"
105
+ bundle exec rspec --format RspecJunitFormatter --out "tmp/rspec${TEST_ENV_NUMBER}.xml"
106
+ ```
107
+
108
+ Use distinct Buildkite step keys for this example; add a build identifier too if
109
+ you combine reports across builds.
84
110
 
85
111
  ### Capturing output
86
112
 
@@ -115,109 +141,21 @@ Note that this example captures all output from every example all the time, pote
115
141
 
116
142
  [xml-charsets]: https://www.w3.org/TR/xml/#charsets
117
143
 
118
- ## Development
119
-
120
- Use Ruby 3.4 (see [`.ruby-version`](.ruby-version)) for maintenance. This selects the development Ruby; the installed gem's minimum is defined in the [gemspec](rspec_junit_formatter.gemspec).
121
-
122
- Install the Bundler version recorded in `Gemfile.lock`, then test the locked development bundle:
123
-
124
- ```sh
125
- gem install bundler -v 4.0.23
126
- bundle install
127
- bundle exec rake spec
128
- ```
144
+ ## Supported versions and legacy upgrades
129
145
 
130
- The [development workflow](.github/workflows/development.yml) tests this bundle separately from the [compatibility workflow](.github/workflows/ci.yml). Use `bundle update --all` to refresh the development lockfile.
146
+ We support [maintained Ruby versions](https://www.ruby-lang.org/en/downloads/branches/), including security maintenance, and [the current stable RSpec series](https://rspec.info/blog/2024/11/introducing-the-monorepo/). Minor releases may raise the minimum versions when upstream support ends.
131
147
 
132
- The supported RSpec series is also defined by [Appraisal][appraisal], with a separate Gemfile to test dependency resolution without the root lockfile:
148
+ For Ruby < 3.3 or RSpec < 3.13, use [0.6.2](https://rubygems.org/gems/rspec_junit_formatter/versions/0.6.2), the final legacy release:
133
149
 
134
- ```sh
135
- BUNDLE_GEMFILE=gemfiles/rspec_3_13.gemfile bundle install
136
- BUNDLE_GEMFILE=gemfiles/rspec_3_13.gemfile bundle exec rake spec
150
+ ```ruby
151
+ gem "rspec_junit_formatter", "0.6.2", require: false
137
152
  ```
138
153
 
139
- Plain `bundle exec rake` also runs the Appraisals outside CI.
154
+ Run `bundle update rspec_junit_formatter`, or install without Bundler using `gem install rspec_junit_formatter -v 0.6.2`. This release permits Ruby >= 2.3 and RSpec >= 2, < 4, except 2.12.0; see its [tested combinations](https://github.com/sj26/rspec_junit_formatter/blob/v0.6.2/README.md#tested-versus-permitted-versions).
140
155
 
141
- [appraisal]: https://github.com/thoughtbot/appraisal
156
+ ## Contributing
142
157
 
143
- ### Tested versus permitted versions
144
-
145
- The [gemspec](rspec_junit_formatter.gemspec) permits Ruby >= 3.3 and `rspec-core` >= 3.13, < 4. These requirements allow installation; future versions are not automatically tested or supported.
146
-
147
- The CI workflows test these combinations:
148
-
149
- | Ruby | RSpec | Bundle |
150
- | --- | --- | --- |
151
- | 3.3, 3.4 and 4.0 | 3.13, latest patch | Appraisal |
152
- | 3.3, 3.4 and 4.0 | 3.13, pinned by `Gemfile.lock` | Development |
153
-
154
- CI selects the latest patch of each listed Ruby branch. The locked bundle currently uses RSpec 3.13.2 and rspec-core 3.13.6. Older Ruby and RSpec combinations remain available in [0.6.2](https://github.com/sj26/rspec_junit_formatter/tree/v0.6.2), not in the current test matrix.
155
-
156
- ## Releasing
157
-
158
- Releases use the [Release workflow](https://github.com/sj26/rspec_junit_formatter/actions/workflows/release.yml)
159
- and [RubyGems trusted publishing](https://guides.rubygems.org/trusted-publishing/).
160
- GitHub Actions builds **unsigned gems**: it needs no personal signing key, persistent
161
- RubyGems API key, or maintainer machine. Local `bundle exec rake build`, `install`,
162
- and `release` tasks remain available; local releases may retain the maintainer's
163
- traditional gem signatures. Do not publish the same version through both paths.
164
-
165
- 1. Bump the gemspec version and update the changelog in a reviewed PR. Merge it
166
- into `main`. Use a new stable `X.Y.Z` version; never reuse a published version.
167
- 2. Tag that exact commit as `vX.Y.Z` and push the tag. Record its full commit SHA
168
- with `git rev-parse 'vX.Y.Z^{commit}'`. Pushing a tag alone does not publish.
169
- 3. Run the Release workflow **from `main`**, supplying the tag and full SHA. Leave
170
- `publish` unchecked for validation only. It checks tag/version/commit agreement
171
- and main ancestry, runs the compatibility and development suites at that SHA,
172
- builds the gem, rejects embedded signatures, and tests a clean installation
173
- outside the checkout. PRs also exercise the package build/install check, but
174
- their artifacts can never be published by this workflow.
175
- 4. Run again with the same tag and SHA and `publish` checked. After the checks pass,
176
- review the commit and package SHA-256 in the build log, then approve the
177
- `release` environment deployment. Sam can approve his own run. The publish job
178
- downloads that run's exact immutable artifact, uses short-lived OIDC credentials,
179
- and does not check out or build project code.
180
- 5. The workflow verifies that RubyGems serves identical bytes before publishing a
181
- GitHub release with the same gem. Check both the
182
- [RubyGems version](https://rubygems.org/gems/rspec_junit_formatter/versions) and
183
- [GitHub release](https://github.com/sj26/rspec_junit_formatter/releases).
184
-
185
- ### One-time configuration
186
-
187
- The GitHub `release` environment must require approval by `sj26`, allow
188
- self-approval, disable admin bypass, and allow deployments only from the `main`
189
- branch. The **Immutable release tags** repository ruleset blocks updates and
190
- deletion of `v*` tags, with no bypass actors. Under the existing gem's
191
- **Trusted publishers** on RubyGems, configure:
192
-
193
- | Field | Value |
194
- | --- | --- |
195
- | Repository owner | `sj26` |
196
- | Repository name | `rspec_junit_formatter` |
197
- | Workflow filename | `release.yml` |
198
- | Environment | `release` |
199
-
200
- Leave reusable-workflow repository fields blank. This workflow grants
201
- `id-token: write` only to the environment-protected publish job. Do not add a
202
- RubyGems API key or personal signing key to repository secrets.
203
-
204
- ### Recovery
205
-
206
- Release runs are serialized and never auto-cancel an active publication. If a
207
- push succeeds but a later step fails, **re-run failed jobs in the original run**
208
- while its `release-gem` artifact is retained (30 days). This reuses the validated
209
- bytes and requires environment approval again. An existing RubyGems version is
210
- accepted only if its SHA-256 matches, and its downloaded bytes must also match.
211
- An incomplete GitHub draft can resume; an existing asset must match byte-for-byte
212
- and is never overwritten. A published GitHub release missing its gem fails closed
213
- and needs manual inspection rather than silently changing a public release.
214
-
215
- If the tag moved, a digest differs, or the original artifact expired, stop and
216
- investigate. Do not delete/recreate tags, yank/re-push gems, or overwrite release
217
- assets to force a retry. A fresh run rebuilds and is safe only if it reproduces
218
- the same bytes; otherwise recover the original artifact or use a new version
219
- through the normal review process. Validation-only runs do not test OIDC exchange
220
- or publication; only a real, explicitly approved new release can prove those paths.
158
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development, testing and release instructions.
221
159
 
222
160
  ## License
223
161
 
@@ -85,11 +85,13 @@ private
85
85
  def failure_for(notification)
86
86
  exception = exception_for(notification)
87
87
 
88
- if aggregate_failure?(exception)
88
+ details = if aggregate_failure?(exception)
89
89
  strip_diff_colors(exception.message)
90
90
  else
91
91
  strip_diff_colors(notification.message_lines.join("\n")) << "\n" << notification.formatted_backtrace.join("\n")
92
92
  end
93
+
94
+ details.gsub(/ +$/, '')
93
95
  end
94
96
 
95
97
  def exception_for(notification)
@@ -11,10 +11,20 @@ require "rspec/core/formatters/base_formatter"
11
11
  class RSpecJUnitFormatter < RSpec::Core::Formatters::BaseFormatter
12
12
  private
13
13
 
14
+ def suite_name
15
+ # parallel_tests deliberately uses an empty label for its first worker.
16
+ suffix = ENV.fetch("TEST_ENV_NUMBER") do
17
+ %w[CI_NODE_INDEX CIRCLE_NODE_INDEX SEMAPHORE_JOB_INDEX
18
+ BUILDKITE_PARALLEL_JOB BITBUCKET_PARALLEL_STEP].map { |name| ENV[name] }.
19
+ find { |value| value && !value.empty? }
20
+ end
21
+ "rspec#{suffix}"
22
+ end
23
+
14
24
  def xml_dump
15
25
  output << %{<?xml version="1.0" encoding="UTF-8"?>\n}
16
26
  output << %{<testsuite}
17
- output << %{ name="rspec#{escape(ENV["TEST_ENV_NUMBER"].to_s)}"}
27
+ output << %{ name="#{escape(suite_name)}"}
18
28
  output << %{ tests="#{example_count}"}
19
29
  output << %{ skipped="#{pending_count}"}
20
30
  output << %{ failures="#{failure_count}"}
@@ -162,9 +172,13 @@ private
162
172
  ?>.freeze => "&gt;".freeze,
163
173
  ).freeze
164
174
 
175
+ def utf8(text)
176
+ text.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "\uFFFD")
177
+ end
178
+
165
179
  def escape(text)
166
180
  # Make sure it's utf-8, replace illegal characters with ruby-like escapes, and replace special and discouraged characters with entities
167
- text.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "\uFFFD").gsub(ILLEGAL_REGEXP, ILLEGAL_REPLACEMENT).gsub(DISCOURAGED_REGEXP, DISCOURAGED_REPLACEMENTS)
181
+ utf8(text).gsub(ILLEGAL_REGEXP, ILLEGAL_REPLACEMENT).gsub(DISCOURAGED_REGEXP, DISCOURAGED_REPLACEMENTS)
168
182
  end
169
183
 
170
184
  STRIP_DIFF_COLORS_BLOCK_REGEXP = /^ ( [ ]* ) Diff: (?: \e\[ 0 m )? (?: \n \1 \e\[ \d+ (?: ; \d+ )* m .* )* /x
@@ -180,7 +194,9 @@ private
180
194
  # We also only want to target the diff hunks because the failure message
181
195
  # itself might legitimately contain ansi escape codes.
182
196
  #
183
- string.sub(STRIP_DIFF_COLORS_BLOCK_REGEXP) { |match| match.gsub(STRIP_DIFF_COLORS_CODES_REGEXP, "".freeze) }
197
+ # Transcode before matching: the original message may be invalid or use an
198
+ # encoding incompatible with the ASCII regexp. Leave XML escaping until later.
199
+ utf8(string).sub(STRIP_DIFF_COLORS_BLOCK_REGEXP) { |match| match.gsub(STRIP_DIFF_COLORS_CODES_REGEXP, "".freeze) }
184
200
  end
185
201
  end
186
202
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec_junit_formatter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Cochran