rspec-capturing-formatter 1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: be7b57d6badad05069b5d4bed0fc557a75ddd0de583aa4b5c64a45bd21c96766
4
+ data.tar.gz: 1633b80c3ea60608b64401b4dca9e92c3d9ebb7ca11bcf18b4e7ce2021894037
5
+ SHA512:
6
+ metadata.gz: 2cc8b9cc8124ce483eb6b130b61256340c6891c06ec4c6483b39177825022433bf58f9378c089bca4991070c3a3b5fb025662cd5e2da37a8faa7f112b330fe9b
7
+ data.tar.gz: 3cd510e72dedce015cb6ab9fc9fd5a76513d93511f609fa72e902d0cc90395e93070d2e6e13470a91343ee908ffd7b8dfb7f3d79aee862791bf210c463e74fd5
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hongli Lai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # rspec-capturing-formatter
2
+
3
+ `rspec-capturing-formatter` is an RSpec formatter that keeps test progress readable when examples write logs to stdout or stderr. This is especially helpful in CI, where interleaved output can make it difficult to tell which example produced a particular log message. By keeping output clearly associated with the example that produced it, the formatter makes failures easier to understand and debug.
4
+
5
+ Example:
6
+
7
+ ```text
8
+ A car › it is red
9
+ ✅ succeeded
10
+
11
+ A car › it beeps when there is danger
12
+ stdout | starting engine
13
+ stderr | warning: beeper initialization delayed
14
+ ✅ succeeded 812 ms
15
+
16
+ A car › during maintenance mode
17
+ suite stdout | preparing maintenance fixtures
18
+
19
+ A car › during maintenance mode › it disables safety guardrails
20
+ stdout | frobnicating the door
21
+ ❌ failed 2.00 s
22
+ rerun | rspec ./spec/car_spec.rb:20
23
+
24
+ Failure/Error: raise "test"
25
+
26
+ RuntimeError:
27
+ test
28
+
29
+ diagnostic state: maintenance
30
+
31
+ # ./spec/car_spec.rb:20
32
+
33
+ 1 deprecation warning total
34
+
35
+ Summary
36
+ 3 total 2 succeeded 1 failed 0 pending
37
+ Finished in 2.90 s | files loaded in 98.9 ms
38
+
39
+ Failed examples
40
+ rspec ./spec/car_spec.rb:20
41
+ ```
42
+
43
+ Color and emphasis are omitted from this example. Actual output uses color and emoji when supported.
44
+
45
+ > [!NOTE]
46
+ > On Windows, rerun lines look super weird, like this:
47
+ >
48
+ > ```
49
+ > rerun | ruby -e ^"ARGV^[0^]=ARGV.fetch^(0^).unpack1^(\^"m0\^"^).force_encoding^(\^"UTF-8\^"^)^;load^(Gem.bin_path^(\^"rspec-core\^"^,\^"rspec\^"^)^)^" Li9zcGVjL3VuaXQvd2luZG93c19jb21tYW5kX2xpbmVfc3BlYy5yYjo3Ng==
50
+ > ```
51
+ >
52
+ > This is because Windows cmd.exe escaping rules are super complicated. On Windows, RubyGems-installed commands such as `rspec` are actually `.bat` files, thus involving multiple layers of cmd.exe quoting. It's not as straightforward as on Unix where you just escape the value multiple times.
53
+ >
54
+ > So I gave up. On Windows, rerun commands invoke Ruby directly and contain a Base64-encoded target that doesn't produce any escaping issues. It looks weird, but it works. The LLM gave up trying to solve this problem "properly" after many hours of runs. If you care to improve this, a pull request (with extensive tests) is welcome.
55
+
56
+ ## Usage
57
+
58
+ Add to your Gemfile:
59
+
60
+ ```ruby
61
+ gem "rspec-capturing-formatter"
62
+
63
+ # For terminal color support on Windows
64
+ gem "fiddle", platforms: [:windows]
65
+ ```
66
+
67
+ Add the formatter to `.rspec`:
68
+
69
+ ```text
70
+ --require rspec/capturing_formatter
71
+ --format RSpec::CapturingFormatter
72
+ ```
73
+
74
+ List `rspec/capturing_formatter` before other early-required files that construct loggers. This allows output from those loggers to be included in the report.
75
+
76
+ Before an RSpec run starts, requiring the formatter does not hide or buffer boot output. Code that requires `$stdout` or `$stderr` to be an actual `IO` should not require this gem unless it uses the formatter.
77
+
78
+ Use this as the sole human-readable console formatter. Machine formatters may still write to separate files:
79
+
80
+ ```sh
81
+ bundle exec rspec \
82
+ --require rspec/capturing_formatter \
83
+ --format RSpec::CapturingFormatter \
84
+ --format json --out tmp/rspec.json
85
+ ```
86
+
87
+ ## Configuration
88
+
89
+ Defaults can be changed with a Ruby configuration block:
90
+
91
+ ```ruby
92
+ RSpec::CapturingFormatter.configure do |config|
93
+ config.slow_threshold = 0.5 # seconds; nil disables per-example timing
94
+ config.separator = " › "
95
+ config.color = true
96
+ config.emoji = :auto # :auto, true, or false
97
+ config.pending_failure_output = :full # :full, :no_backtrace, or :skip
98
+ end
99
+ ```
100
+
101
+ When `config.color == true`, coloring is enabled even when there is no TTY. Coloring is always disabled when `NO_COLOR` or when RSpec's `--no-color` option is set.
102
+
103
+ On Windows, interactive coloring is supported in Windows Terminal. Legacy Windows console sessions are not supported color targets and receive plain-text output instead of raw ANSI escape sequences. Redirected reports keep ANSI coloring when enabled, so they can be viewed by ANSI-aware CI logs or tools.
104
+
105
+ ## Output capture limitations
106
+
107
+ The formatter captures writes made through `$stdout` and `$stderr` after the formatter is loaded. This includes calls to bare `puts`, `print`, `warn`, and calls to typical Ruby `Logger` instances that log to stdout or stderr.
108
+
109
+ This formatter does not capture direct native writes to file descriptors 1 or 2, or output written by subprocesses.
110
+
111
+ Same-process parallel example runners are unsupported because the formatter cannot reliably assign simultaneous stdout and stderr writes to individual examples. Multi-process runners may be used, but the runner is responsible for keeping each process's report together.
data/Rakefile ADDED
@@ -0,0 +1,80 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task default: :spec
4
+
5
+ desc "Run RSpec code examples"
6
+ task :spec do
7
+ rspec_args = []
8
+
9
+ if ENV["DOGFOOD"]
10
+ rspec_args << "--require"
11
+ rspec_args << "./lib/rspec/capturing_formatter"
12
+ rspec_args << "--format"
13
+ rspec_args << "RSpec::CapturingFormatter"
14
+ end
15
+ if ENV["FORCE_COLOR"]
16
+ rspec_args << "--force-color"
17
+ end
18
+ if (file = ENV["FILE"])
19
+ rspec_args << file
20
+ end
21
+ if (example = ENV["EXAMPLE"])
22
+ rspec_args << "-e"
23
+ rspec_args << example
24
+ end
25
+
26
+ ruby(*[
27
+ "--enable=frozen-string-literal",
28
+ "-S",
29
+ "rspec",
30
+ rspec_args
31
+ ].flatten.compact)
32
+ end
33
+
34
+ def tested_rspec_versions
35
+ @tested_rspec_versions ||= Dir["gemfiles/*.gemfile"].map do |path|
36
+ File.basename(path).sub(/\..*/, "")
37
+ end
38
+ end
39
+
40
+ desc "Run bundle install for all Gemfiles"
41
+ task "bundle:install" => [
42
+ "bundle:install:main",
43
+ tested_rspec_versions.map { |rspec_version| "bundle:install:#{rspec_version}" }
44
+ ].flatten
45
+
46
+ desc "Run bundle update for all Gemfiles"
47
+ task "bundle:update" => [
48
+ "bundle:update:main",
49
+ tested_rspec_versions.map { |rspec_version| "bundle:update:#{rspec_version}" }
50
+ ].flatten
51
+
52
+ desc "Run bundle install for main Gemfile"
53
+ task "bundle:install:main" do
54
+ Bundler.with_unbundled_env do
55
+ sh "bundle install"
56
+ end
57
+ end
58
+
59
+ desc "Run bundle update for main Gemfile"
60
+ task "bundle:update:main" do
61
+ Bundler.with_unbundled_env do
62
+ sh "bundle update --all"
63
+ end
64
+ end
65
+
66
+ tested_rspec_versions.each do |rspec_version|
67
+ desc "Run bundle install for #{rspec_version} Gemfile"
68
+ task "bundle:install:#{rspec_version}" do
69
+ Bundler.with_unbundled_env do
70
+ sh "env BUNDLE_GEMFILE=gemfiles/#{Shellwords.escape(rspec_version)}.gemfile bundle install"
71
+ end
72
+ end
73
+
74
+ desc "Run bundle update for #{rspec_version} Gemfile"
75
+ task "bundle:update:#{rspec_version}" do
76
+ Bundler.with_unbundled_env do
77
+ sh "env BUNDLE_GEMFILE=gemfiles/#{Shellwords.escape(rspec_version)}.gemfile bundle update --all"
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ class CapturingFormatter
5
+ class Configuration
6
+ attr_reader :slow_threshold, :separator, :color, :emoji
7
+
8
+ def initialize
9
+ @slow_threshold = 0.5
10
+ @separator = " › "
11
+ @color = true
12
+ @emoji = :auto
13
+ @pending_failure_output = nil
14
+ end
15
+
16
+ def slow_threshold=(value)
17
+ valid = value.nil? || (value.is_a?(Numeric) && value >= 0)
18
+ unless valid
19
+ raise ArgumentError, "slow_threshold must be a non-negative number or nil"
20
+ end
21
+
22
+ @slow_threshold = value
23
+ end
24
+
25
+ def separator=(value)
26
+ unless value.is_a?(String) && !value.empty?
27
+ raise ArgumentError, "separator must be a non-empty string"
28
+ end
29
+
30
+ @separator = value
31
+ end
32
+
33
+ def color=(value)
34
+ unless value == true || value == false
35
+ raise ArgumentError, "color must be true or false"
36
+ end
37
+
38
+ @color = value
39
+ end
40
+
41
+ def emoji=(value)
42
+ unless [:auto, true, false].include?(value)
43
+ raise ArgumentError, "emoji must be :auto, true, or false"
44
+ end
45
+
46
+ @emoji = value
47
+ end
48
+
49
+ # Keep "unset" distinct from explicit :full so CapturingFormatter can inherit RSpec's setting.
50
+ def pending_failure_output=(value)
51
+ unless [:full, :no_backtrace, :skip].include?(value)
52
+ raise ArgumentError, "pending_failure_output must be :full, :no_backtrace, or :skip"
53
+ end
54
+
55
+ @pending_failure_output = value
56
+ end
57
+
58
+ def pending_failure_output
59
+ @pending_failure_output || :full
60
+ end
61
+
62
+ def pending_failure_output_configured?
63
+ !@pending_failure_output.nil?
64
+ end
65
+
66
+ def to_h
67
+ {
68
+ slow_threshold: @slow_threshold,
69
+ separator: @separator,
70
+ color: @color,
71
+ emoji: @emoji,
72
+ pending_failure_output: pending_failure_output
73
+ }
74
+ end
75
+ end
76
+ end
77
+ end