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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: efd2cce37a69f805e637ea406088049b150bb0ff9192a7423100add8b7a29342
4
- data.tar.gz: 4e25659b95836fc83544b295cf40b6428d094cf194356ff1e4e0c0b8e7c1ad42
3
+ metadata.gz: 89b93a80d709137ae29eb42cfda731bd4196ad40cfc4e56f2b3a12fc2961eee9
4
+ data.tar.gz: 49b1f5f622b6a1796013f0c370391a5e3941f6ffb67b8fb15b6b762470b8a093
5
5
  SHA512:
6
- metadata.gz: 72682994c9e8647d1405a46aa15366c377325f31396767f6f6efe0ef6e6722f875bdce839a0094c04db309e39cb691a05d3daa43d5b4aeff2372430f1e69cee1
7
- data.tar.gz: 51ee62f1212076d50984ed31c221760464fbd2f15990abd8f4c241fd4294bcc259bffa5b9c46ce54942ab6a5b178582272cd9c09de08dae7fa67baa874bf7e0b
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
- # AngryIo
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
- 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.
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, AngryIo is always on, and every test that writes to `$stdout` or `$stderr` now raises:
32
+ By default, AngryIO is always on, and every test that writes to `$stdout` or `$stderr` now raises:
33
33
 
34
34
  ```
35
- IOError: AngryIo::Stream is not writable: "your output here"
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, but fail on CI — you can configure like this:
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
- AngryIo.configure do |config|
45
- config.enabled = -> { ENV["CI"] == "true" }
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
- `AngryIo.configure` yields a config struct with one field:
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
- `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`.
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 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).
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).
@@ -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
- # AngryIo::Stream instances around each test. A test class opts out by calling
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 AngryIo
9
+ module AngryIO
10
10
  module Minitest
11
11
  module Adapter
12
12
  def run
13
- AngryIo.around_streams(opted_out: self.class.i_absolutely_need_to_write_to_stdout?) { super }
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
- AngryIo.with_real_streams { super }
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
- AngryIo.hook_active_support_stream!
41
+ AngryIO.hook_active_support_stream!
42
42
  end
43
43
  end
44
44
  end
45
45
 
46
46
  require "minitest"
47
- AngryIo::Minitest.setup!
47
+ AngryIO::Minitest.setup!
@@ -2,11 +2,11 @@
2
2
 
3
3
  require "angry_io"
4
4
 
5
- # Registers an +around+ hook that swaps $stdout/$stderr to AngryIo::Stream
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 AngryIo
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
- AngryIo.with_real_streams { super }
18
+ AngryIO.with_real_streams { super }
19
19
  end
20
20
 
21
21
  def to_stderr_from_any_process
22
- AngryIo.with_real_streams { super }
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
- AngryIo.with_real_streams { super }
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
- AngryIo.around_streams(opted_out: opt_out) { example.run }
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
- AngryIo.hook_active_support_stream!
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
- AngryIo::RSpec.setup!
52
+ AngryIO::RSpec.setup!
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module AngryIo
4
- VERSION = "0.4.0"
3
+ module AngryIO
4
+ VERSION = "0.5.0"
5
5
  end
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 AngryIo
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, "AngryIo::Stream is not writable: #{string.to_s.inspect}"
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, "AngryIo::Stream is not writable: #{char.inspect}"
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
- AngryIo.with_real_streams { super }
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 AngryIo it's the StringIO
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 = AngryIo.real_stream_for(stream)
82
- AngryIo.with_real_streams { super(real || stream) }
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
- Config = Struct.new(:enabled) do
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
- attr_reader :config
98
-
99
- def configure
100
- yield @config
101
- end
91
+ attr_accessor :enabled
102
92
 
103
- # Swap $stdout and $stderr to AngryIo::Stream instances around the given
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 +config.enabled+ returns false, so the block
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 config.enabled.call
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
- # AngryIo::Stream buffers afterwards. Helpers like Minitest's
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 AngryIo::Stream buffers, return the
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.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: AngryIo replaces $stdout/$stderr during tests with an IO that raises
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.