matchi 5.0.0 → 6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66ca7f8e6eb1fb3e7fa80907957f37af41fd04b678258199c0f1abfe784aa839
4
- data.tar.gz: b25bc8c4da4bb7db33478cf7fda77e7119bf0fce94fdfd8310ab8d7c79f8638c
3
+ metadata.gz: 5f6e92545e779fe4ef4a899d77213b30b26480bb4ee4daef969b1e1ea60ee7e5
4
+ data.tar.gz: a407ea85374dd3242d28da5609fe37320200879edb46c877a13dba1f3e377d66
5
5
  SHA512:
6
- metadata.gz: 5c4b77eb03979138e162a7040380b9b74c31b16e1995e67e537eebd3d83074913fa39d377f29759e085f35a143a15f19407f77dcd6b2788fc5e2874b28f3591f
7
- data.tar.gz: e570be50f7b9338a24489b791b0b118b0c892fcc6c8fdc7f375b6187cf63a430642d6a87c7b7016f28b9b6742e7a8c4c07cbcf94bf340a9d590dfbeb49ef0e87
6
+ metadata.gz: b48397862d2c8d6c8582e23445cf8c73f03f714db6bd7a73cd950f6ccc8149f8488440a827c7e31c220927e2dd7c294cc8726cd2afa1c5fb99dad2f2d8bec930
7
+ data.tar.gz: 5160fa06299005d522a20e8f805bd16ae5bddec4341728b92f15bbfe64c8582d756328326c29c6868636c99a9f87af74d4ee81f93e9b61f2d20a6b692f0db0cb
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  Framework-agnostic matchers for Ruby. Small, explicit, and written so that the object under test cannot forge a match.
8
8
 
9
- ![A Rubyist juggling between Matchi letters](https://github.com/fixrb/matchi/raw/main/img/matchi.png)
9
+ ![A pixel-art jester juggling the letters of Matchi](https://github.com/fixrb/matchi/raw/main/img/matchi.png)
10
10
 
11
11
  ## Installation
12
12
 
@@ -91,9 +91,9 @@ Matchi::Change.new(list, :length).from(1).to(2).match? { list << 1 } # => true
91
91
 
92
92
  `BeWithin.new(delta)` and `Change#from(value)` are builders, not matchers: they do not respond to `match?` until `of` or `to` completes them.
93
93
 
94
- Arguments are validated when the matcher is built (`ArgumentError`); a name is resolved when the matcher runs (`NameError`, or `TypeError` when the constant is not a module). A block is always required by `match?`. Whatever the actual value or an observed method raises propagates; `RaiseException` catches what its block raises, not what its message filter or the resolution of its name raises.
94
+ Arguments are validated when the matcher is built (`ArgumentError`); a name is resolved when the matcher runs (`NameError`, or `TypeError` when the constant is not a module, or not an exception class for `RaiseException`). A block is always required by `match?`. Whatever the actual value or an observed method raises propagates; `RaiseException` rescues what its block raises, within the limits below, not what its message filter or the resolution of its name raises.
95
95
 
96
- `RaiseException` catches every `Exception`, including `SystemExit` and `Interrupt`, so that a specification can assert on them; an unexpected `exit` inside the block is a failure, not the end of the process.
96
+ `RaiseException` rescues an exception of the expected class, and any `StandardError`, `ScriptError`, `SystemStackError` or `SystemExit` (the exceptions [Defi](https://fixrb.dev/gems/defi/) keeps too), which it then compares with the expected class. Anything else, an `Interrupt`, a signal, a `NoMemoryError`, the exception with which an enclosing `Timeout` ends its block, goes through as it would with any other matcher, unless that is the class expected: `RaiseException.new(Interrupt)` rescues an `Interrupt`. An unexpected `exit` inside the block is the wrong exception, so `false`, not the end of the process.
97
97
 
98
98
  `Change` observes a value: when the observed method returns the same unfrozen object twice (an `attr_reader` on a mutated array, say), `match?` raises instead of reporting no change. Observe `items.length`, or use `to`.
99
99
 
@@ -130,11 +130,16 @@ If you come from RSpec or Minitest, these will surprise you:
130
130
 
131
131
  - `Eq` is RSpec's `eql`, not its `eq`: it compares with `eql?`, so `1` is not `1.0`, and a value object that only defines `==` never matches. Define `eql?` and `hash` on it, or use `Satisfy`. `Integer#==` hands the comparison to the other operand, which is why Matchi does not use it.
132
132
  - `Match` takes a `Regexp` only. For a literal substring, `Matchi::Match.new(Regexp.new(Regexp.escape("a.b")))`.
133
- - `RaiseException` is `raise_error`; it needs an exception class, or its name, and takes an optional message.
133
+ - `RaiseException` is `raise_error`; it needs an exception class, or its name, and takes an optional message. It does not rescue an `Interrupt`, a signal, a `NoMemoryError` or the exception with which an enclosing `Timeout` ends its block, unless that is the class expected, where `raise_error` rescues every `Exception`.
134
134
  - `Change#to` demands a change: a value already equal to the target does not pass. `Change` without a refinement needs the observed method to return a value, not the mutated object.
135
135
  - `Predicate` only calls a method of the actual value: `be_nil` calls `nil?`, but `be_truthy`, `be_falsey` and `be_a` are not predicates and do not exist. Use `Be.new(nil)`, `Satisfy`, `BeAKindOf`.
136
136
  - There is no `and`, `or`, `not_to` or `all`: compose in your framework, with plain booleans.
137
137
 
138
+ ## Changes in 6.0
139
+
140
+ - `RaiseException` no longer rescues every `Exception`: it rescues the expected class, and any `StandardError`, `ScriptError`, `SystemStackError` or `SystemExit`, which it compares with the expected class. An `Interrupt`, a signal, a `NoMemoryError` or the exception with which an enclosing `Timeout` ends its block goes through unless that is the class expected, where 5.0 reported it as a failed match. `RaiseException.new(Exception)` still rescues everything, as asked.
141
+ - The message filter of `RaiseException` and `Match` compare a plain copy of the actual `String`: a subclass could answer `valid_encoding?` for it and turn the comparison into an error.
142
+
138
143
  ## Changes in 5.0
139
144
 
140
145
  - `BeAKindOf`, `BeAnInstanceOf` and `RaiseException` accept a module directly, so anonymous classes work. A name is still accepted; it must be a constant path (`"Foo::bar"` is rejected at construction) and resolve to a module, or a class for `BeAnInstanceOf` and an exception class for `RaiseException`. `BeAnInstanceOf` rejects a module that is not a class.
data/lib/matchi/match.rb CHANGED
@@ -31,7 +31,8 @@ module Matchi
31
31
  actual = yield
32
32
  return false unless ::String === actual || ::Symbol === actual
33
33
 
34
- string = ::Symbol === actual ? actual.name : actual
34
+ # A plain copy: a subclass of String has no say in the comparison.
35
+ string = ::String.new(::Symbol === actual ? actual.name : actual)
35
36
  return false unless string.valid_encoding?
36
37
 
37
38
  begin
@@ -9,11 +9,13 @@ module Matchi
9
9
  # message: a String the message must be `eql?` to, or a Regexp it must
10
10
  # match.
11
11
  #
12
- # Every `Exception` is caught, including `SystemExit` and `Interrupt`: a
13
- # specification must be able to assert on any of them, so an unexpected
14
- # `exit` or Ctrl-C inside the block is reported as a failure, not
15
- # propagated. The expected class can be given directly, or by name; see
16
- # {Matchi::Constant}.
12
+ # The matcher rescues an exception of the expected class, and any
13
+ # `StandardError`, `ScriptError`, `SystemStackError` or `SystemExit`,
14
+ # which it then compares with the expected class. Anything else, an
15
+ # `Interrupt`, a signal, a `NoMemoryError`, the exception with which an
16
+ # enclosing `Timeout` ends its block, goes through as it would with any
17
+ # other matcher, unless that is the class expected. The expected class can
18
+ # be given directly, or by name; see {Matchi::Constant}.
17
19
  #
18
20
  # @example
19
21
  # Matchi::RaiseException.new(ZeroDivisionError).match? { 1 / 0 } # => true
@@ -45,11 +47,15 @@ module Matchi
45
47
  # mistaken for an exception raised by the block.
46
48
  klass = expected_class
47
49
 
50
+ # The expected class first, whatever it is; then what a block raises
51
+ # on its own, the exceptions Defi keeps too.
48
52
  begin
49
53
  yield
50
54
  false
51
- rescue ::Exception => e # rubocop:disable Lint/RescueException
52
- klass === e && message?(e)
55
+ rescue klass => e
56
+ message?(e)
57
+ rescue ::StandardError, ::ScriptError, ::SystemStackError, ::SystemExit
58
+ false
53
59
  end
54
60
  end
55
61
 
@@ -85,13 +91,17 @@ module Matchi
85
91
  end
86
92
 
87
93
  # The message is whatever the exception answers to `message`, which is
88
- # its own data, as with any exception; it must be a valid String. The
89
- # expected String or Regexp receives the comparison.
94
+ # its own data, as with any exception; it must be a String, copied so
95
+ # that a subclass has no say, and valid. The expected String or Regexp
96
+ # receives the comparison.
90
97
  def message?(exception)
91
98
  return true if @message.nil?
92
99
 
93
100
  message = exception.message
94
- return false unless ::String === message && message.valid_encoding?
101
+ return false unless ::String === message
102
+
103
+ message = ::String.new(message)
104
+ return false unless message.valid_encoding?
95
105
 
96
106
  ::String === @message ? @message.eql?(message) : @message.match?(message)
97
107
  rescue ::Encoding::CompatibilityError
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: matchi
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-10-18 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: 'Matchi provides small, explicit expectation matchers for any Ruby testing
14
13
  framework. The expected value always controls the comparison, so the object under
@@ -45,10 +44,10 @@ licenses:
45
44
  - MIT
46
45
  metadata:
47
46
  bug_tracker_uri: https://github.com/fixrb/matchi/issues
47
+ changelog_uri: https://github.com/fixrb/matchi#changes-in-60
48
48
  documentation_uri: https://rubydoc.info/gems/matchi
49
49
  rubygems_mfa_required: 'true'
50
50
  source_code_uri: https://github.com/fixrb/matchi
51
- post_install_message:
52
51
  rdoc_options: []
53
52
  require_paths:
54
53
  - lib
@@ -63,8 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
62
  - !ruby/object:Gem::Version
64
63
  version: '0'
65
64
  requirements: []
66
- rubygems_version: 3.5.22
67
- signing_key:
65
+ rubygems_version: 4.0.23
68
66
  specification_version: 4
69
67
  summary: Framework-agnostic matchers for secure Ruby testing
70
68
  test_files: []