angry_io 0.4.0 → 0.5.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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +11 -16
- data/lib/angry_io/minitest.rb +6 -6
- data/lib/angry_io/rspec.rb +8 -8
- data/lib/angry_io/version.rb +2 -2
- data/lib/angry_io.rb +14 -24
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 89b93a80d709137ae29eb42cfda731bd4196ad40cfc4e56f2b3a12fc2961eee9
|
|
4
|
+
data.tar.gz: 49b1f5f622b6a1796013f0c370391a5e3941f6ffb67b8fb15b6b762470b8a093
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80fbb90830d2836b63b73302b5a7f12dad52a8a05353290cc286b5493cd75446bd343f2c660f373716d0beb6e2b130014502751213fb22fb37e36c8d6d193650
|
|
7
|
+
data.tar.gz: 29f6e481c7e4dde2c6e45db4ae6c03d6621e496673565144b79149057e3d74343f40ad05669bf7e381f166efb64c7ce238d681a4a60d907e4f3d16e5dafe7384
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-09-17
|
|
4
|
+
|
|
5
|
+
- Replace `AngryIO.configure` / `AngryIO::Config` with a direct `AngryIO.enabled` accessor (still a callable, default `-> { true }`). This is a breaking change: replace `AngryIO.configure { |c| c.enabled = ... }` with `AngryIO.enabled = ...`.
|
|
6
|
+
- Rename the core module from `AngryIo` to `AngryIO` (`AngryIO::Stream`, `AngryIO.enabled`, `AngryIO.with_real_streams`, etc.). This is a breaking change: no backwards-compatibility alias is provided, so any code referencing the old `AngryIo` constant must be updated. The lowercase gem name (`angry_io`) and require paths (`angry_io/rspec`, `angry_io/minitest`) are unchanged.
|
|
7
|
+
|
|
3
8
|
## [0.4.0] - 2026-09-15
|
|
4
9
|
|
|
5
10
|
- Reopening `$stdout`/`$stderr` onto a real IO (e.g. `$stdout.reopen(File::NULL)`) no longer raises `TypeError` under `AngryIo::Stream`. `Stream#reopen` now delegates an IO argument to the original real stream it replaced (a `dup2` that survives the caller closing the other IO) and hands the global back so writes reach the reopened IO; non-IO arguments still defer to StringIO's own buffer reset. The new `AngryIo.real_stream_for` returns the real stream a swapped buffer replaced.
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AngryIO
|
|
2
2
|
|
|
3
3
|
Friends don't let friends write tests or specs that output to stdout/stderr. When you look at your test output, you should only see green dots, right?
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
AngryIO replaces `$stdout` and `$stderr` during your test suite with an IO that _raises on write_, so accidental output fails loudly instead of quietly cluttering your output. It ships self-registering adapters for both RSpec and Minitest, gated by a configurable `enabled` callable.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -29,21 +29,20 @@ Depending on your test framework, you should require either `angry_io/rspec` or
|
|
|
29
29
|
require "angry_io/rspec" # or "angry_io/minitest"
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
By default,
|
|
32
|
+
By default, AngryIO is always on, and every test that writes to `$stdout` or `$stderr` now raises:
|
|
33
33
|
|
|
34
34
|
```
|
|
35
|
-
IOError:
|
|
35
|
+
IOError: AngryIO::Stream is not writable: "your output here"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
Zero-byte writes (e.g. `$stderr.print("")`) emit nothing, so they are allowed.
|
|
39
39
|
|
|
40
|
-
If you want to allow test output in some environments but not in others — e.g., allow output when testing locally
|
|
40
|
+
If you want to allow test output in some environments but not in others — e.g., fail on CI, but allow output when testing locally so REPL debuggers like `binding.irb` or `pry` work — set the `enabled` predicate:
|
|
41
41
|
|
|
42
42
|
```ruby
|
|
43
|
-
# On by default; turn it off in environments where you'll allow real output
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
end
|
|
43
|
+
# On by default; turn it off in environments where you'll allow real output
|
|
44
|
+
# (e.g. locally, so a debugger can write to stdout without raising).
|
|
45
|
+
AngryIO.enabled = -> { ENV["CI"] == "true" }
|
|
47
46
|
```
|
|
48
47
|
|
|
49
48
|
### Opting out
|
|
@@ -83,15 +82,11 @@ end
|
|
|
83
82
|
|
|
84
83
|
## Configuration
|
|
85
84
|
|
|
86
|
-
`
|
|
87
|
-
|
|
88
|
-
| Field | Default | Description |
|
|
89
|
-
| --- | --- | --- |
|
|
90
|
-
| `enabled` | `-> { true }` | A callable returning whether AngryIo is active. Invoked once per test, so it can read env vars or feature flags live. |
|
|
85
|
+
`AngryIO.enabled` is a callable returning whether AngryIO is active. It defaults to `-> { true }` and is invoked once per test, so it can read env vars or feature flags live — e.g. `-> { ENV["CI"] == "true" }` to enforce on CI while leaving local debuggers usable.
|
|
91
86
|
|
|
92
87
|
## How it works
|
|
93
88
|
|
|
94
|
-
`
|
|
89
|
+
`AngryIO::Stream` is a `StringIO` whose write methods raise an `IOError` when given anything but an empty string. Around each test, the adapter swaps `$stdout` and `$stderr` to fresh `AngryIO::Stream` instances and restores the originals (closing the buffers) in an `ensure`.
|
|
95
90
|
|
|
96
91
|
## Development
|
|
97
92
|
|
|
@@ -113,4 +108,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
113
108
|
|
|
114
109
|
## Code of Conduct
|
|
115
110
|
|
|
116
|
-
Everyone interacting in the
|
|
111
|
+
Everyone interacting in the AngryIO project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/bquorning/angry_io/blob/main/CODE_OF_CONDUCT.md).
|
data/lib/angry_io/minitest.rb
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
require "angry_io"
|
|
4
4
|
|
|
5
5
|
# Prepends an override of +Minitest::Test#run+ that swaps $stdout/$stderr to
|
|
6
|
-
#
|
|
6
|
+
# AngryIO::Stream instances around each test. A test class opts out by calling
|
|
7
7
|
# +i_absolutely_need_to_write_to_stdout!+ in its body. Force-loads Minitest so
|
|
8
8
|
# registration works regardless of Bundler.require ordering.
|
|
9
|
-
module
|
|
9
|
+
module AngryIO
|
|
10
10
|
module Minitest
|
|
11
11
|
module Adapter
|
|
12
12
|
def run
|
|
13
|
-
|
|
13
|
+
AngryIO.around_streams(opted_out: self.class.i_absolutely_need_to_write_to_stdout?) { super }
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -30,7 +30,7 @@ module AngryIo
|
|
|
30
30
|
# real streams for the duration of the capture.
|
|
31
31
|
module CaptureSubprocessIo
|
|
32
32
|
def capture_subprocess_io(&block)
|
|
33
|
-
|
|
33
|
+
AngryIO.with_real_streams { super }
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -38,10 +38,10 @@ module AngryIo
|
|
|
38
38
|
::Minitest::Test.extend(ClassMethods)
|
|
39
39
|
::Minitest::Test.prepend(Adapter)
|
|
40
40
|
::Minitest::Assertions.prepend(CaptureSubprocessIo)
|
|
41
|
-
|
|
41
|
+
AngryIO.hook_active_support_stream!
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
require "minitest"
|
|
47
|
-
|
|
47
|
+
AngryIO::Minitest.setup!
|
data/lib/angry_io/rspec.rb
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require "angry_io"
|
|
4
4
|
|
|
5
|
-
# Registers an +around+ hook that swaps $stdout/$stderr to
|
|
5
|
+
# Registers an +around+ hook that swaps $stdout/$stderr to AngryIO::Stream
|
|
6
6
|
# instances during each example, unless the example opts out via the
|
|
7
7
|
# +:i_absolutely_need_to_write_to_stdout+ metadata. Force-loads rspec-core so
|
|
8
8
|
# registration works regardless of Bundler.require ordering.
|
|
9
|
-
module
|
|
9
|
+
module AngryIO
|
|
10
10
|
module RSpec
|
|
11
11
|
# The to_*_from_any_process matchers reopen $stdout/$stderr onto Tempfiles
|
|
12
12
|
# so subprocesses inherit the file descriptors, which fails on
|
|
@@ -15,11 +15,11 @@ module AngryIo
|
|
|
15
15
|
# stream...
|
|
16
16
|
module FromAnyProcess
|
|
17
17
|
def to_stdout_from_any_process
|
|
18
|
-
|
|
18
|
+
AngryIO.with_real_streams { super }
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def to_stderr_from_any_process
|
|
22
|
-
|
|
22
|
+
AngryIO.with_real_streams { super }
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
@@ -27,7 +27,7 @@ module AngryIo
|
|
|
27
27
|
# are captured by the Tempfile instead of raising against the Angry stream.
|
|
28
28
|
module CaptureStreamToTempfile
|
|
29
29
|
def capture(block)
|
|
30
|
-
|
|
30
|
+
AngryIO.with_real_streams { super }
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -36,17 +36,17 @@ module AngryIo
|
|
|
36
36
|
config.around do |example|
|
|
37
37
|
opt_out = example.metadata.key?(:i_absolutely_need_to_write_to_stdout) &&
|
|
38
38
|
example.metadata[:i_absolutely_need_to_write_to_stdout]
|
|
39
|
-
|
|
39
|
+
AngryIO.around_streams(opted_out: opt_out) { example.run }
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
::RSpec::Matchers::BuiltIn::Output.prepend(FromAnyProcess)
|
|
44
44
|
::RSpec::Matchers::BuiltIn::CaptureStreamToTempfile.prepend(CaptureStreamToTempfile)
|
|
45
|
-
|
|
45
|
+
AngryIO.hook_active_support_stream!
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
require "rspec/core"
|
|
51
51
|
require "rspec/expectations"
|
|
52
|
-
|
|
52
|
+
AngryIO::RSpec.setup!
|
data/lib/angry_io/version.rb
CHANGED
data/lib/angry_io.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "stringio"
|
|
4
4
|
require_relative "angry_io/version"
|
|
5
5
|
|
|
6
|
-
module
|
|
6
|
+
module AngryIO
|
|
7
7
|
# An IO that raises when you write to it, so tests can't silently pollute
|
|
8
8
|
# stdout/stderr. Zero-byte writes like `$stderr.print("")` emit nothing and
|
|
9
9
|
# are allowed; anything that would produce output raises an IOError.
|
|
@@ -18,7 +18,7 @@ module AngryIo
|
|
|
18
18
|
strings.each do |string|
|
|
19
19
|
next if string.to_s.empty?
|
|
20
20
|
|
|
21
|
-
raise IOError, "
|
|
21
|
+
raise IOError, "AngryIO::Stream is not writable: \"#{string}\""
|
|
22
22
|
end
|
|
23
23
|
super
|
|
24
24
|
end
|
|
@@ -26,7 +26,7 @@ module AngryIo
|
|
|
26
26
|
# StringIO#putc writes directly in C, bypassing #write. It always emits a
|
|
27
27
|
# byte, so it always refuses.
|
|
28
28
|
def putc(char)
|
|
29
|
-
raise IOError, "
|
|
29
|
+
raise IOError, "AngryIO::Stream is not writable: \"#{char}\""
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# `IO#reopen(io)` redirects a real $stdout/$stderr onto another IO (a
|
|
@@ -69,44 +69,34 @@ module AngryIo
|
|
|
69
69
|
# capture/silence, the same way Minitest's capture_subprocess_io is handled.
|
|
70
70
|
module ActiveSupportStreamCapture
|
|
71
71
|
def capture(stream)
|
|
72
|
-
|
|
72
|
+
AngryIO.with_real_streams { super }
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def silence_stream(stream)
|
|
76
|
-
# `stream` is bound at call time, so under
|
|
76
|
+
# `stream` is bound at call time, so under AngryIO it's the StringIO
|
|
77
77
|
# buffer — which AS can't actually reopen onto IO::NULL (it just resets
|
|
78
78
|
# the buffer), leaking the block's writes to the real stream. Substitute
|
|
79
79
|
# the real stream it replaced so AS silences (and the block writes to) the
|
|
80
80
|
# real IO.
|
|
81
|
-
real =
|
|
82
|
-
|
|
81
|
+
real = AngryIO.real_stream_for(stream)
|
|
82
|
+
AngryIO.with_real_streams { super(real || stream) }
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
private :capture, :silence_stream
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
def initialize
|
|
90
|
-
self.enabled = -> { true }
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
@config = Config.new
|
|
88
|
+
@enabled = -> { true }
|
|
95
89
|
|
|
96
90
|
class << self
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def configure
|
|
100
|
-
yield @config
|
|
101
|
-
end
|
|
91
|
+
attr_accessor :enabled
|
|
102
92
|
|
|
103
|
-
# Swap $stdout and $stderr to
|
|
93
|
+
# Swap $stdout and $stderr to AngryIO::Stream instances around the given
|
|
104
94
|
# block, restoring them (and closing the buffers) in an ensure. No-ops when
|
|
105
|
-
# +opted_out+ is true or when +
|
|
95
|
+
# +opted_out+ is true or when +enabled+ returns false, so the block
|
|
106
96
|
# runs untouched.
|
|
107
97
|
def around_streams(opted_out: false)
|
|
108
98
|
return yield if opted_out
|
|
109
|
-
return yield unless
|
|
99
|
+
return yield unless enabled.call
|
|
110
100
|
|
|
111
101
|
original_stdout = $stdout
|
|
112
102
|
original_stderr = $stderr
|
|
@@ -129,7 +119,7 @@ module AngryIo
|
|
|
129
119
|
end
|
|
130
120
|
|
|
131
121
|
# Run the block with the pre-swap $stdout/$stderr restored, re-swapping the
|
|
132
|
-
#
|
|
122
|
+
# AngryIO::Stream buffers afterwards. Helpers like Minitest's
|
|
133
123
|
# capture_subprocess_io need this: they reopen $stdout/$stderr onto
|
|
134
124
|
# Tempfiles so subprocesses inherit the file descriptors, which only works
|
|
135
125
|
# on real IOs. No-ops when no swap is active, or when the real streams are
|
|
@@ -154,7 +144,7 @@ module AngryIo
|
|
|
154
144
|
end
|
|
155
145
|
end
|
|
156
146
|
|
|
157
|
-
# If `stream` is one of the active swap's
|
|
147
|
+
# If `stream` is one of the active swap's AngryIO::Stream buffers, return the
|
|
158
148
|
# real stream it replaced; otherwise nil. Used by
|
|
159
149
|
# ActiveSupportStreamCapture#silence_stream to redirect silencing onto the
|
|
160
150
|
# real IO instead of the StringIO buffer.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: angry_io
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Benjamin Quorning
|
|
@@ -9,7 +9,7 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
|
-
description:
|
|
12
|
+
description: AngryIO replaces $stdout/$stderr during tests with an IO that raises
|
|
13
13
|
on write, so accidental output fails loudly instead of cluttering CI logs. Ships
|
|
14
14
|
RSpec and Minitest adapters that self-register, gated by a configurable enabled
|
|
15
15
|
callable.
|