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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +80 -0
- data/lib/rspec/capturing_formatter/configuration.rb +77 -0
- data/lib/rspec/capturing_formatter/renderer.rb +457 -0
- data/lib/rspec/capturing_formatter/sanitizer.rb +378 -0
- data/lib/rspec/capturing_formatter/stream_proxy.rb +414 -0
- data/lib/rspec/capturing_formatter/version.rb +9 -0
- data/lib/rspec/capturing_formatter/windows_command_line.rb +65 -0
- data/lib/rspec/capturing_formatter/windows_terminal.rb +87 -0
- data/lib/rspec/capturing_formatter.rb +319 -0
- metadata +120 -0
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
|