@ajksunkang-aios/kgraph-linux-x64 0.1.2 → 0.1.3

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.
Files changed (196) hide show
  1. package/bin/kgraph-launcher +15 -3
  2. package/lib/kgraph/scripts/build-bundle.sh +17 -4
  3. package/lib/site-packages/outcome/__init__.py +20 -0
  4. package/lib/site-packages/outcome/_impl.py +239 -0
  5. package/lib/site-packages/outcome/_util.py +33 -0
  6. package/lib/site-packages/outcome/_version.py +7 -0
  7. package/lib/site-packages/outcome/py.typed +0 -0
  8. package/lib/site-packages/outcome-1.3.0.post0.dist-info/INSTALLER +1 -0
  9. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE +3 -0
  10. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.APACHE2 +202 -0
  11. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.MIT +20 -0
  12. package/lib/site-packages/outcome-1.3.0.post0.dist-info/METADATA +63 -0
  13. package/lib/site-packages/outcome-1.3.0.post0.dist-info/RECORD +13 -0
  14. package/lib/site-packages/outcome-1.3.0.post0.dist-info/WHEEL +6 -0
  15. package/lib/site-packages/outcome-1.3.0.post0.dist-info/top_level.txt +1 -0
  16. package/lib/site-packages/sniffio/__init__.py +17 -0
  17. package/lib/site-packages/sniffio/_impl.py +95 -0
  18. package/lib/site-packages/sniffio/_tests/__init__.py +0 -0
  19. package/lib/site-packages/sniffio/_tests/test_sniffio.py +84 -0
  20. package/lib/site-packages/sniffio/_version.py +3 -0
  21. package/lib/site-packages/sniffio/py.typed +0 -0
  22. package/lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER +1 -0
  23. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE +3 -0
  24. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 +202 -0
  25. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT +20 -0
  26. package/lib/site-packages/sniffio-1.3.1.dist-info/METADATA +104 -0
  27. package/lib/site-packages/sniffio-1.3.1.dist-info/RECORD +14 -0
  28. package/lib/site-packages/sniffio-1.3.1.dist-info/WHEEL +5 -0
  29. package/lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt +1 -0
  30. package/lib/site-packages/sortedcontainers/__init__.py +74 -0
  31. package/lib/site-packages/sortedcontainers/sorteddict.py +812 -0
  32. package/lib/site-packages/sortedcontainers/sortedlist.py +2646 -0
  33. package/lib/site-packages/sortedcontainers/sortedset.py +733 -0
  34. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/INSTALLER +1 -0
  35. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/LICENSE +13 -0
  36. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/METADATA +264 -0
  37. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/RECORD +10 -0
  38. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/WHEEL +6 -0
  39. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/top_level.txt +1 -0
  40. package/lib/site-packages/trio/__init__.py +133 -0
  41. package/lib/site-packages/trio/__main__.py +3 -0
  42. package/lib/site-packages/trio/_abc.py +714 -0
  43. package/lib/site-packages/trio/_channel.py +610 -0
  44. package/lib/site-packages/trio/_core/__init__.py +94 -0
  45. package/lib/site-packages/trio/_core/_asyncgens.py +243 -0
  46. package/lib/site-packages/trio/_core/_concat_tb.py +26 -0
  47. package/lib/site-packages/trio/_core/_entry_queue.py +223 -0
  48. package/lib/site-packages/trio/_core/_exceptions.py +169 -0
  49. package/lib/site-packages/trio/_core/_generated_instrumentation.py +50 -0
  50. package/lib/site-packages/trio/_core/_generated_io_epoll.py +98 -0
  51. package/lib/site-packages/trio/_core/_generated_io_kqueue.py +153 -0
  52. package/lib/site-packages/trio/_core/_generated_io_windows.py +204 -0
  53. package/lib/site-packages/trio/_core/_generated_run.py +269 -0
  54. package/lib/site-packages/trio/_core/_generated_windows_ffi.py +10 -0
  55. package/lib/site-packages/trio/_core/_instrumentation.py +117 -0
  56. package/lib/site-packages/trio/_core/_io_common.py +31 -0
  57. package/lib/site-packages/trio/_core/_io_epoll.py +385 -0
  58. package/lib/site-packages/trio/_core/_io_kqueue.py +292 -0
  59. package/lib/site-packages/trio/_core/_io_windows.py +1036 -0
  60. package/lib/site-packages/trio/_core/_ki.py +271 -0
  61. package/lib/site-packages/trio/_core/_local.py +104 -0
  62. package/lib/site-packages/trio/_core/_mock_clock.py +165 -0
  63. package/lib/site-packages/trio/_core/_parking_lot.py +317 -0
  64. package/lib/site-packages/trio/_core/_run.py +3148 -0
  65. package/lib/site-packages/trio/_core/_run_context.py +15 -0
  66. package/lib/site-packages/trio/_core/_tests/__init__.py +0 -0
  67. package/lib/site-packages/trio/_core/_tests/test_asyncgen.py +339 -0
  68. package/lib/site-packages/trio/_core/_tests/test_cancelled.py +222 -0
  69. package/lib/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py +103 -0
  70. package/lib/site-packages/trio/_core/_tests/test_guest_mode.py +755 -0
  71. package/lib/site-packages/trio/_core/_tests/test_instrumentation.py +315 -0
  72. package/lib/site-packages/trio/_core/_tests/test_io.py +522 -0
  73. package/lib/site-packages/trio/_core/_tests/test_ki.py +703 -0
  74. package/lib/site-packages/trio/_core/_tests/test_local.py +118 -0
  75. package/lib/site-packages/trio/_core/_tests/test_mock_clock.py +193 -0
  76. package/lib/site-packages/trio/_core/_tests/test_parking_lot.py +389 -0
  77. package/lib/site-packages/trio/_core/_tests/test_run.py +3024 -0
  78. package/lib/site-packages/trio/_core/_tests/test_thread_cache.py +227 -0
  79. package/lib/site-packages/trio/_core/_tests/test_tutil.py +13 -0
  80. package/lib/site-packages/trio/_core/_tests/test_unbounded_queue.py +154 -0
  81. package/lib/site-packages/trio/_core/_tests/test_windows.py +305 -0
  82. package/lib/site-packages/trio/_core/_tests/tutil.py +117 -0
  83. package/lib/site-packages/trio/_core/_tests/type_tests/nursery_start.py +79 -0
  84. package/lib/site-packages/trio/_core/_tests/type_tests/run.py +51 -0
  85. package/lib/site-packages/trio/_core/_thread_cache.py +317 -0
  86. package/lib/site-packages/trio/_core/_traps.py +318 -0
  87. package/lib/site-packages/trio/_core/_unbounded_queue.py +163 -0
  88. package/lib/site-packages/trio/_core/_wakeup_socketpair.py +75 -0
  89. package/lib/site-packages/trio/_core/_windows_cffi.py +313 -0
  90. package/lib/site-packages/trio/_deprecate.py +171 -0
  91. package/lib/site-packages/trio/_dtls.py +1380 -0
  92. package/lib/site-packages/trio/_file_io.py +513 -0
  93. package/lib/site-packages/trio/_highlevel_generic.py +125 -0
  94. package/lib/site-packages/trio/_highlevel_open_tcp_listeners.py +251 -0
  95. package/lib/site-packages/trio/_highlevel_open_tcp_stream.py +397 -0
  96. package/lib/site-packages/trio/_highlevel_open_unix_stream.py +65 -0
  97. package/lib/site-packages/trio/_highlevel_serve_listeners.py +148 -0
  98. package/lib/site-packages/trio/_highlevel_socket.py +423 -0
  99. package/lib/site-packages/trio/_highlevel_ssl_helpers.py +180 -0
  100. package/lib/site-packages/trio/_path.py +289 -0
  101. package/lib/site-packages/trio/_repl.py +159 -0
  102. package/lib/site-packages/trio/_signals.py +185 -0
  103. package/lib/site-packages/trio/_socket.py +1326 -0
  104. package/lib/site-packages/trio/_ssl.py +964 -0
  105. package/lib/site-packages/trio/_subprocess.py +1178 -0
  106. package/lib/site-packages/trio/_subprocess_platform/__init__.py +123 -0
  107. package/lib/site-packages/trio/_subprocess_platform/kqueue.py +48 -0
  108. package/lib/site-packages/trio/_subprocess_platform/waitid.py +113 -0
  109. package/lib/site-packages/trio/_subprocess_platform/windows.py +11 -0
  110. package/lib/site-packages/trio/_sync.py +908 -0
  111. package/lib/site-packages/trio/_tests/__init__.py +0 -0
  112. package/lib/site-packages/trio/_tests/astrill-codesigning-cert.cer +0 -0
  113. package/lib/site-packages/trio/_tests/check_type_completeness.py +247 -0
  114. package/lib/site-packages/trio/_tests/module_with_deprecations.py +22 -0
  115. package/lib/site-packages/trio/_tests/pytest_plugin.py +54 -0
  116. package/lib/site-packages/trio/_tests/test_abc.py +72 -0
  117. package/lib/site-packages/trio/_tests/test_channel.py +750 -0
  118. package/lib/site-packages/trio/_tests/test_contextvars.py +56 -0
  119. package/lib/site-packages/trio/_tests/test_deprecate.py +277 -0
  120. package/lib/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py +64 -0
  121. package/lib/site-packages/trio/_tests/test_dtls.py +950 -0
  122. package/lib/site-packages/trio/_tests/test_exports.py +626 -0
  123. package/lib/site-packages/trio/_tests/test_fakenet.py +317 -0
  124. package/lib/site-packages/trio/_tests/test_file_io.py +269 -0
  125. package/lib/site-packages/trio/_tests/test_highlevel_generic.py +98 -0
  126. package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py +419 -0
  127. package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py +693 -0
  128. package/lib/site-packages/trio/_tests/test_highlevel_open_unix_stream.py +86 -0
  129. package/lib/site-packages/trio/_tests/test_highlevel_serve_listeners.py +186 -0
  130. package/lib/site-packages/trio/_tests/test_highlevel_socket.py +336 -0
  131. package/lib/site-packages/trio/_tests/test_highlevel_ssl_helpers.py +169 -0
  132. package/lib/site-packages/trio/_tests/test_path.py +279 -0
  133. package/lib/site-packages/trio/_tests/test_repl.py +428 -0
  134. package/lib/site-packages/trio/_tests/test_scheduler_determinism.py +47 -0
  135. package/lib/site-packages/trio/_tests/test_signals.py +186 -0
  136. package/lib/site-packages/trio/_tests/test_socket.py +1253 -0
  137. package/lib/site-packages/trio/_tests/test_ssl.py +1371 -0
  138. package/lib/site-packages/trio/_tests/test_subprocess.py +767 -0
  139. package/lib/site-packages/trio/_tests/test_sync.py +735 -0
  140. package/lib/site-packages/trio/_tests/test_testing.py +682 -0
  141. package/lib/site-packages/trio/_tests/test_testing_raisesgroup.py +1128 -0
  142. package/lib/site-packages/trio/_tests/test_threads.py +1173 -0
  143. package/lib/site-packages/trio/_tests/test_timeouts.py +281 -0
  144. package/lib/site-packages/trio/_tests/test_tracing.py +88 -0
  145. package/lib/site-packages/trio/_tests/test_trio.py +8 -0
  146. package/lib/site-packages/trio/_tests/test_unix_pipes.py +288 -0
  147. package/lib/site-packages/trio/_tests/test_util.py +349 -0
  148. package/lib/site-packages/trio/_tests/test_wait_for_object.py +225 -0
  149. package/lib/site-packages/trio/_tests/test_windows_pipes.py +112 -0
  150. package/lib/site-packages/trio/_tests/tools/__init__.py +0 -0
  151. package/lib/site-packages/trio/_tests/tools/test_gen_exports.py +179 -0
  152. package/lib/site-packages/trio/_tests/tools/test_mypy_annotate.py +140 -0
  153. package/lib/site-packages/trio/_tests/tools/test_sync_requirements.py +80 -0
  154. package/lib/site-packages/trio/_tests/type_tests/check_wraps.py +9 -0
  155. package/lib/site-packages/trio/_tests/type_tests/open_memory_channel.py +4 -0
  156. package/lib/site-packages/trio/_tests/type_tests/path.py +140 -0
  157. package/lib/site-packages/trio/_tests/type_tests/subprocesses.py +23 -0
  158. package/lib/site-packages/trio/_tests/type_tests/task_status.py +29 -0
  159. package/lib/site-packages/trio/_threads.py +610 -0
  160. package/lib/site-packages/trio/_timeouts.py +197 -0
  161. package/lib/site-packages/trio/_tools/__init__.py +0 -0
  162. package/lib/site-packages/trio/_tools/gen_exports.py +401 -0
  163. package/lib/site-packages/trio/_tools/mypy_annotate.py +126 -0
  164. package/lib/site-packages/trio/_tools/sync_requirements.py +98 -0
  165. package/lib/site-packages/trio/_tools/windows_ffi_build.py +220 -0
  166. package/lib/site-packages/trio/_unix_pipes.py +197 -0
  167. package/lib/site-packages/trio/_util.py +385 -0
  168. package/lib/site-packages/trio/_version.py +3 -0
  169. package/lib/site-packages/trio/_wait_for_object.py +67 -0
  170. package/lib/site-packages/trio/_windows_pipes.py +144 -0
  171. package/lib/site-packages/trio/abc.py +23 -0
  172. package/lib/site-packages/trio/from_thread.py +13 -0
  173. package/lib/site-packages/trio/lowlevel.py +95 -0
  174. package/lib/site-packages/trio/py.typed +0 -0
  175. package/lib/site-packages/trio/socket.py +602 -0
  176. package/lib/site-packages/trio/testing/__init__.py +58 -0
  177. package/lib/site-packages/trio/testing/_check_streams.py +570 -0
  178. package/lib/site-packages/trio/testing/_checkpoints.py +69 -0
  179. package/lib/site-packages/trio/testing/_fake_net.py +584 -0
  180. package/lib/site-packages/trio/testing/_memory_streams.py +633 -0
  181. package/lib/site-packages/trio/testing/_network.py +36 -0
  182. package/lib/site-packages/trio/testing/_raises_group.py +1015 -0
  183. package/lib/site-packages/trio/testing/_sequencer.py +87 -0
  184. package/lib/site-packages/trio/testing/_trio_test.py +50 -0
  185. package/lib/site-packages/trio/to_thread.py +4 -0
  186. package/lib/site-packages/trio-0.33.0.dist-info/INSTALLER +1 -0
  187. package/lib/site-packages/trio-0.33.0.dist-info/METADATA +186 -0
  188. package/lib/site-packages/trio-0.33.0.dist-info/RECORD +156 -0
  189. package/lib/site-packages/trio-0.33.0.dist-info/REQUESTED +0 -0
  190. package/lib/site-packages/trio-0.33.0.dist-info/WHEEL +5 -0
  191. package/lib/site-packages/trio-0.33.0.dist-info/entry_points.txt +2 -0
  192. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE +3 -0
  193. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.APACHE2 +202 -0
  194. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.MIT +22 -0
  195. package/lib/site-packages/trio-0.33.0.dist-info/top_level.txt +1 -0
  196. package/package.json +1 -1
@@ -0,0 +1,1128 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import sys
5
+ from types import TracebackType
6
+
7
+ import pytest
8
+
9
+ import trio
10
+ from trio.testing import _Matcher as Matcher, _RaisesGroup as RaisesGroup
11
+ from trio.testing._raises_group import repr_callable
12
+
13
+ if sys.version_info < (3, 11):
14
+ from exceptiongroup import BaseExceptionGroup, ExceptionGroup
15
+
16
+
17
+ def wrap_escape(s: str) -> str:
18
+ return "^" + re.escape(s) + "$"
19
+
20
+
21
+ def fails_raises_group(
22
+ msg: str, add_prefix: bool = True
23
+ ) -> pytest.RaisesExc[AssertionError]:
24
+ assert (
25
+ msg[-1] != "\n"
26
+ ), "developer error, expected string should not end with newline"
27
+ prefix = "Raised exception group did not match: " if add_prefix else ""
28
+ return pytest.raises(AssertionError, match=wrap_escape(prefix + msg))
29
+
30
+
31
+ def test_raises_group() -> None:
32
+ with pytest.raises(
33
+ ValueError,
34
+ match=wrap_escape(
35
+ f'Invalid argument "{TypeError()!r}" must be exception type, Matcher, or RaisesGroup.',
36
+ ),
37
+ ):
38
+ RaisesGroup(TypeError()) # type: ignore[call-overload]
39
+ with RaisesGroup(ValueError):
40
+ raise ExceptionGroup("foo", (ValueError(),))
41
+
42
+ with (
43
+ fails_raises_group("'SyntaxError' is not of type 'ValueError'"),
44
+ RaisesGroup(ValueError),
45
+ ):
46
+ raise ExceptionGroup("foo", (SyntaxError(),))
47
+
48
+ # multiple exceptions
49
+ with RaisesGroup(ValueError, SyntaxError):
50
+ raise ExceptionGroup("foo", (ValueError(), SyntaxError()))
51
+
52
+ # order doesn't matter
53
+ with RaisesGroup(SyntaxError, ValueError):
54
+ raise ExceptionGroup("foo", (ValueError(), SyntaxError()))
55
+
56
+ # nested exceptions
57
+ with RaisesGroup(RaisesGroup(ValueError)):
58
+ raise ExceptionGroup("foo", (ExceptionGroup("bar", (ValueError(),)),))
59
+
60
+ with RaisesGroup(
61
+ SyntaxError,
62
+ RaisesGroup(ValueError),
63
+ RaisesGroup(RuntimeError),
64
+ ):
65
+ raise ExceptionGroup(
66
+ "foo",
67
+ (
68
+ SyntaxError(),
69
+ ExceptionGroup("bar", (ValueError(),)),
70
+ ExceptionGroup("", (RuntimeError(),)),
71
+ ),
72
+ )
73
+
74
+
75
+ def test_incorrect_number_exceptions() -> None:
76
+ # We previously gave an error saying the number of exceptions was wrong,
77
+ # but we now instead indicate excess/missing exceptions
78
+ with (
79
+ fails_raises_group(
80
+ "1 matched exception. Unexpected exception(s): [RuntimeError()]"
81
+ ),
82
+ RaisesGroup(ValueError),
83
+ ):
84
+ raise ExceptionGroup("", (RuntimeError(), ValueError()))
85
+
86
+ # will error if there's missing exceptions
87
+ with (
88
+ fails_raises_group(
89
+ "1 matched exception. Too few exceptions raised, found no match for: ['SyntaxError']"
90
+ ),
91
+ RaisesGroup(ValueError, SyntaxError),
92
+ ):
93
+ raise ExceptionGroup("", (ValueError(),))
94
+
95
+ with (
96
+ fails_raises_group(
97
+ "\n"
98
+ "1 matched exception. \n"
99
+ "Too few exceptions raised!\n"
100
+ "The following expected exceptions did not find a match:\n"
101
+ " 'ValueError'\n"
102
+ " It matches ValueError() which was paired with 'ValueError'"
103
+ ),
104
+ RaisesGroup(ValueError, ValueError),
105
+ ):
106
+ raise ExceptionGroup("", (ValueError(),))
107
+
108
+ with (
109
+ fails_raises_group(
110
+ "\n"
111
+ "1 matched exception. \n"
112
+ "Unexpected exception(s)!\n"
113
+ "The following raised exceptions did not find a match\n"
114
+ " ValueError():\n"
115
+ " It matches 'ValueError' which was paired with ValueError()"
116
+ ),
117
+ RaisesGroup(ValueError),
118
+ ):
119
+ raise ExceptionGroup("", (ValueError(), ValueError()))
120
+
121
+ with (
122
+ fails_raises_group(
123
+ "\n"
124
+ "1 matched exception. \n"
125
+ "The following expected exceptions did not find a match:\n"
126
+ " 'ValueError'\n"
127
+ " It matches ValueError() which was paired with 'ValueError'\n"
128
+ "The following raised exceptions did not find a match\n"
129
+ " SyntaxError():\n"
130
+ " 'SyntaxError' is not of type 'ValueError'"
131
+ ),
132
+ RaisesGroup(ValueError, ValueError),
133
+ ):
134
+ raise ExceptionGroup("", [ValueError(), SyntaxError()])
135
+
136
+
137
+ def test_flatten_subgroups() -> None:
138
+ # loose semantics, as with expect*
139
+ with RaisesGroup(ValueError, flatten_subgroups=True):
140
+ raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
141
+
142
+ with RaisesGroup(ValueError, TypeError, flatten_subgroups=True):
143
+ raise ExceptionGroup("", (ExceptionGroup("", (ValueError(), TypeError())),))
144
+ with RaisesGroup(ValueError, TypeError, flatten_subgroups=True):
145
+ raise ExceptionGroup("", [ExceptionGroup("", [ValueError()]), TypeError()])
146
+
147
+ # mixed loose is possible if you want it to be at least N deep
148
+ with RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)):
149
+ raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
150
+ with RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)):
151
+ raise ExceptionGroup(
152
+ "",
153
+ (ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)),),
154
+ )
155
+
156
+ # but not the other way around
157
+ with pytest.raises(
158
+ ValueError,
159
+ match=r"^You cannot specify a nested structure inside a RaisesGroup with",
160
+ ):
161
+ RaisesGroup(RaisesGroup(ValueError), flatten_subgroups=True) # type: ignore[call-overload]
162
+
163
+ # flatten_subgroups is not sufficient to catch fully unwrapped
164
+ with (
165
+ fails_raises_group(
166
+ "'ValueError' is not an exception group, but would match with `allow_unwrapped=True`"
167
+ ),
168
+ RaisesGroup(ValueError, flatten_subgroups=True),
169
+ ):
170
+ raise ValueError
171
+ with (
172
+ fails_raises_group(
173
+ "RaisesGroup(ValueError, flatten_subgroups=True): 'ValueError' is not an exception group, but would match with `allow_unwrapped=True`"
174
+ ),
175
+ RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)),
176
+ ):
177
+ raise ExceptionGroup("", (ValueError(),))
178
+
179
+ # helpful suggestion if flatten_subgroups would make it pass
180
+ with (
181
+ fails_raises_group(
182
+ "Raised exception group did not match: \n"
183
+ "The following expected exceptions did not find a match:\n"
184
+ " 'ValueError'\n"
185
+ " 'TypeError'\n"
186
+ "The following raised exceptions did not find a match\n"
187
+ " ExceptionGroup('', [ValueError(), TypeError()]):\n"
188
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
189
+ " Unexpected nested 'ExceptionGroup', expected 'TypeError'\n"
190
+ "Did you mean to use `flatten_subgroups=True`?",
191
+ add_prefix=False,
192
+ ),
193
+ RaisesGroup(ValueError, TypeError),
194
+ ):
195
+ raise ExceptionGroup("", [ExceptionGroup("", [ValueError(), TypeError()])])
196
+ # but doesn't consider check (otherwise we'd break typing guarantees)
197
+ with (
198
+ fails_raises_group(
199
+ "Raised exception group did not match: \n"
200
+ "The following expected exceptions did not find a match:\n"
201
+ " 'ValueError'\n"
202
+ " 'TypeError'\n"
203
+ "The following raised exceptions did not find a match\n"
204
+ " ExceptionGroup('', [ValueError(), TypeError()]):\n"
205
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
206
+ " Unexpected nested 'ExceptionGroup', expected 'TypeError'\n"
207
+ "Did you mean to use `flatten_subgroups=True`?",
208
+ add_prefix=False,
209
+ ),
210
+ RaisesGroup(
211
+ ValueError,
212
+ TypeError,
213
+ check=lambda eg: len(eg.exceptions) == 1,
214
+ ),
215
+ ):
216
+ raise ExceptionGroup("", [ExceptionGroup("", [ValueError(), TypeError()])])
217
+ # correct number of exceptions, and flatten_subgroups would make it pass
218
+ # This now doesn't print a repr of the caught exception at all, but that can be found in the traceback
219
+ with (
220
+ fails_raises_group(
221
+ "Raised exception group did not match: Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
222
+ " Did you mean to use `flatten_subgroups=True`?",
223
+ add_prefix=False,
224
+ ),
225
+ RaisesGroup(ValueError),
226
+ ):
227
+ raise ExceptionGroup("", [ExceptionGroup("", [ValueError()])])
228
+ # correct number of exceptions, but flatten_subgroups wouldn't help, so we don't suggest it
229
+ with (
230
+ fails_raises_group("Unexpected nested 'ExceptionGroup', expected 'ValueError'"),
231
+ RaisesGroup(ValueError),
232
+ ):
233
+ raise ExceptionGroup("", [ExceptionGroup("", [TypeError()])])
234
+
235
+ # flatten_subgroups can be suggested if nested. This will implicitly ask the user to
236
+ # do `RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True))` which is unlikely
237
+ # to be what they actually want - but I don't think it's worth trying to special-case
238
+ with (
239
+ fails_raises_group(
240
+ "RaisesGroup(ValueError): Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
241
+ " Did you mean to use `flatten_subgroups=True`?",
242
+ ),
243
+ RaisesGroup(RaisesGroup(ValueError)),
244
+ ):
245
+ raise ExceptionGroup(
246
+ "",
247
+ [ExceptionGroup("", [ExceptionGroup("", [ValueError()])])],
248
+ )
249
+
250
+ # Don't mention "unexpected nested" if expecting an ExceptionGroup.
251
+ # Although it should perhaps be an error to specify `RaisesGroup(ExceptionGroup)` in
252
+ # favor of doing `RaisesGroup(RaisesGroup(...))`.
253
+ with (
254
+ fails_raises_group("'BaseExceptionGroup' is not of type 'ExceptionGroup'"),
255
+ RaisesGroup(ExceptionGroup),
256
+ ):
257
+ raise BaseExceptionGroup("", [BaseExceptionGroup("", [KeyboardInterrupt()])])
258
+
259
+
260
+ def test_catch_unwrapped_exceptions() -> None:
261
+ # Catches lone exceptions with strict=False
262
+ # just as except* would
263
+ with RaisesGroup(ValueError, allow_unwrapped=True):
264
+ raise ValueError
265
+
266
+ # expecting multiple unwrapped exceptions is not possible
267
+ with pytest.raises(
268
+ ValueError,
269
+ match=r"^You cannot specify multiple exceptions with",
270
+ ):
271
+ RaisesGroup(SyntaxError, ValueError, allow_unwrapped=True) # type: ignore[call-overload]
272
+ # if users want one of several exception types they need to use a Matcher
273
+ # (which the error message suggests)
274
+ with RaisesGroup(
275
+ Matcher(check=lambda e: isinstance(e, (SyntaxError, ValueError))),
276
+ allow_unwrapped=True,
277
+ ):
278
+ raise ValueError
279
+
280
+ # Unwrapped nested `RaisesGroup` is likely a user error, so we raise an error.
281
+ with pytest.raises(ValueError, match="has no effect when expecting"):
282
+ RaisesGroup(RaisesGroup(ValueError), allow_unwrapped=True) # type: ignore[call-overload]
283
+
284
+ # But it *can* be used to check for nesting level +- 1 if they move it to
285
+ # the nested RaisesGroup. Users should probably use `Matcher`s instead though.
286
+ with RaisesGroup(RaisesGroup(ValueError, allow_unwrapped=True)):
287
+ raise ExceptionGroup("", [ExceptionGroup("", [ValueError()])])
288
+ with RaisesGroup(RaisesGroup(ValueError, allow_unwrapped=True)):
289
+ raise ExceptionGroup("", [ValueError()])
290
+
291
+ # with allow_unwrapped=False (default) it will not be caught
292
+ with (
293
+ fails_raises_group(
294
+ "'ValueError' is not an exception group, but would match with `allow_unwrapped=True`"
295
+ ),
296
+ RaisesGroup(ValueError),
297
+ ):
298
+ raise ValueError("value error text")
299
+
300
+ # allow_unwrapped on its own won't match against nested groups
301
+ with (
302
+ fails_raises_group(
303
+ "Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
304
+ " Did you mean to use `flatten_subgroups=True`?",
305
+ ),
306
+ RaisesGroup(ValueError, allow_unwrapped=True),
307
+ ):
308
+ raise ExceptionGroup("foo", [ExceptionGroup("bar", [ValueError()])])
309
+
310
+ # you need both allow_unwrapped and flatten_subgroups to fully emulate except*
311
+ with RaisesGroup(ValueError, allow_unwrapped=True, flatten_subgroups=True):
312
+ raise ExceptionGroup("", [ExceptionGroup("", [ValueError()])])
313
+
314
+ # code coverage
315
+ with (
316
+ fails_raises_group(
317
+ "Raised exception (group) did not match: 'TypeError' is not of type 'ValueError'",
318
+ add_prefix=False,
319
+ ),
320
+ RaisesGroup(ValueError, allow_unwrapped=True),
321
+ ):
322
+ raise TypeError("this text doesn't show up in the error message")
323
+ with (
324
+ fails_raises_group(
325
+ "Raised exception (group) did not match: Matcher(ValueError): 'TypeError' is not of type 'ValueError'",
326
+ add_prefix=False,
327
+ ),
328
+ RaisesGroup(Matcher(ValueError), allow_unwrapped=True),
329
+ ):
330
+ raise TypeError
331
+
332
+ # check we don't suggest unwrapping with nested RaisesGroup
333
+ with (
334
+ fails_raises_group("'ValueError' is not an exception group"),
335
+ RaisesGroup(RaisesGroup(ValueError)),
336
+ ):
337
+ raise ValueError
338
+
339
+
340
+ def test_match() -> None:
341
+ # supports match string
342
+ with RaisesGroup(ValueError, match="bar"):
343
+ raise ExceptionGroup("bar", (ValueError(),))
344
+
345
+ # now also works with ^$
346
+ with RaisesGroup(ValueError, match="^bar$"):
347
+ raise ExceptionGroup("bar", (ValueError(),))
348
+
349
+ # it also includes notes
350
+ with RaisesGroup(ValueError, match="my note"):
351
+ e = ExceptionGroup("bar", (ValueError(),))
352
+ e.add_note("my note")
353
+ raise e
354
+
355
+ # and technically you can match it all with ^$
356
+ # but you're probably better off using a Matcher at that point
357
+ with RaisesGroup(ValueError, match="^bar\nmy note$"):
358
+ e = ExceptionGroup("bar", (ValueError(),))
359
+ e.add_note("my note")
360
+ raise e
361
+
362
+ with (
363
+ fails_raises_group(
364
+ "Regex pattern 'foo' did not match 'bar' of 'ExceptionGroup'"
365
+ ),
366
+ RaisesGroup(ValueError, match="foo"),
367
+ ):
368
+ raise ExceptionGroup("bar", (ValueError(),))
369
+
370
+ # Suggest a fix for easy pitfall of adding match to the RaisesGroup instead of
371
+ # using a Matcher.
372
+ # This requires a single expected & raised exception, the expected is a type,
373
+ # and `isinstance(raised, expected_type)`.
374
+ with (
375
+ fails_raises_group(
376
+ "Regex pattern 'foo' did not match 'bar' of 'ExceptionGroup', but matched the expected 'ValueError'. You might want RaisesGroup(Matcher(ValueError, match='foo'))"
377
+ ),
378
+ RaisesGroup(ValueError, match="foo"),
379
+ ):
380
+ raise ExceptionGroup("bar", [ValueError("foo")])
381
+
382
+
383
+ def test_check() -> None:
384
+ exc = ExceptionGroup("", (ValueError(),))
385
+
386
+ def is_exc(e: ExceptionGroup[ValueError]) -> bool:
387
+ return e is exc
388
+
389
+ is_exc_repr = repr_callable(is_exc)
390
+ with RaisesGroup(ValueError, check=is_exc):
391
+ raise exc
392
+
393
+ with (
394
+ fails_raises_group(f"check {is_exc_repr} did not return True"),
395
+ RaisesGroup(ValueError, check=is_exc),
396
+ ):
397
+ raise ExceptionGroup("", (ValueError(),))
398
+
399
+
400
+ def test_unwrapped_match_check() -> None:
401
+ def my_check(e: object) -> bool: # pragma: no cover
402
+ return True
403
+
404
+ msg = (
405
+ "`allow_unwrapped=True` bypasses the `match` and `check` parameters"
406
+ " if the exception is unwrapped. If you intended to match/check the"
407
+ " exception you should use a `Matcher` object. If you want to match/check"
408
+ " the exceptiongroup when the exception *is* wrapped you need to"
409
+ " do e.g. `if isinstance(exc.value, ExceptionGroup):"
410
+ " assert RaisesGroup(...).matches(exc.value)` afterwards."
411
+ )
412
+ with pytest.raises(ValueError, match=re.escape(msg)):
413
+ RaisesGroup(ValueError, allow_unwrapped=True, match="foo") # type: ignore[call-overload]
414
+ with pytest.raises(ValueError, match=re.escape(msg)):
415
+ RaisesGroup(ValueError, allow_unwrapped=True, check=my_check) # type: ignore[call-overload]
416
+
417
+ # Users should instead use a Matcher
418
+ rg = RaisesGroup(Matcher(ValueError, match="^foo$"), allow_unwrapped=True)
419
+ with rg:
420
+ raise ValueError("foo")
421
+ with rg:
422
+ raise ExceptionGroup("", [ValueError("foo")])
423
+
424
+ # or if they wanted to match/check the group, do a conditional `.matches()`
425
+ with RaisesGroup(ValueError, allow_unwrapped=True) as exc:
426
+ raise ExceptionGroup("bar", [ValueError("foo")])
427
+ if isinstance(exc.value, ExceptionGroup): # pragma: no branch
428
+ assert RaisesGroup(ValueError, match="bar").matches(exc.value)
429
+
430
+
431
+ def test_RaisesGroup_matches() -> None:
432
+ rg = RaisesGroup(ValueError)
433
+ assert not rg.matches(None)
434
+ assert not rg.matches(ValueError())
435
+ assert rg.matches(ExceptionGroup("", (ValueError(),)))
436
+
437
+
438
+ def test_message() -> None:
439
+ def check_message(
440
+ message: str,
441
+ body: RaisesGroup[BaseException],
442
+ ) -> None:
443
+ with (
444
+ pytest.raises(
445
+ AssertionError,
446
+ match=f"^DID NOT RAISE any exception, expected {re.escape(message)}$",
447
+ ),
448
+ body,
449
+ ):
450
+ ...
451
+
452
+ # basic
453
+ check_message("ExceptionGroup(ValueError)", RaisesGroup(ValueError))
454
+ # multiple exceptions
455
+ check_message(
456
+ "ExceptionGroup(ValueError, ValueError)",
457
+ RaisesGroup(ValueError, ValueError),
458
+ )
459
+ # nested
460
+ check_message(
461
+ "ExceptionGroup(ExceptionGroup(ValueError))",
462
+ RaisesGroup(RaisesGroup(ValueError)),
463
+ )
464
+
465
+ # Matcher
466
+ check_message(
467
+ "ExceptionGroup(Matcher(ValueError, match='my_str'))",
468
+ RaisesGroup(Matcher(ValueError, "my_str")),
469
+ )
470
+ check_message(
471
+ "ExceptionGroup(Matcher(match='my_str'))",
472
+ RaisesGroup(Matcher(match="my_str")),
473
+ )
474
+
475
+ # BaseExceptionGroup
476
+ check_message(
477
+ "BaseExceptionGroup(KeyboardInterrupt)",
478
+ RaisesGroup(KeyboardInterrupt),
479
+ )
480
+ # BaseExceptionGroup with type inside Matcher
481
+ check_message(
482
+ "BaseExceptionGroup(Matcher(KeyboardInterrupt))",
483
+ RaisesGroup(Matcher(KeyboardInterrupt)),
484
+ )
485
+ # Base-ness transfers to parent containers
486
+ check_message(
487
+ "BaseExceptionGroup(BaseExceptionGroup(KeyboardInterrupt))",
488
+ RaisesGroup(RaisesGroup(KeyboardInterrupt)),
489
+ )
490
+ # but not to child containers
491
+ check_message(
492
+ "BaseExceptionGroup(BaseExceptionGroup(KeyboardInterrupt), ExceptionGroup(ValueError))",
493
+ RaisesGroup(RaisesGroup(KeyboardInterrupt), RaisesGroup(ValueError)),
494
+ )
495
+
496
+
497
+ def test_assert_message() -> None:
498
+ # the message does not need to list all parameters to RaisesGroup, nor all exceptions
499
+ # in the exception group, as those are both visible in the traceback.
500
+ # first fails to match
501
+ with (
502
+ fails_raises_group("'TypeError' is not of type 'ValueError'"),
503
+ RaisesGroup(ValueError),
504
+ ):
505
+ raise ExceptionGroup("a", [TypeError()])
506
+ with (
507
+ fails_raises_group(
508
+ "Raised exception group did not match: \n"
509
+ "The following expected exceptions did not find a match:\n"
510
+ " RaisesGroup(ValueError)\n"
511
+ " RaisesGroup(ValueError, match='a')\n"
512
+ "The following raised exceptions did not find a match\n"
513
+ " ExceptionGroup('', [RuntimeError()]):\n"
514
+ " RaisesGroup(ValueError): 'RuntimeError' is not of type 'ValueError'\n"
515
+ " RaisesGroup(ValueError, match='a'): Regex pattern 'a' did not match '' of 'ExceptionGroup'\n"
516
+ " RuntimeError():\n"
517
+ " RaisesGroup(ValueError): 'RuntimeError' is not an exception group\n"
518
+ " RaisesGroup(ValueError, match='a'): 'RuntimeError' is not an exception group",
519
+ add_prefix=False, # to see the full structure
520
+ ),
521
+ RaisesGroup(RaisesGroup(ValueError), RaisesGroup(ValueError, match="a")),
522
+ ):
523
+ raise ExceptionGroup(
524
+ "",
525
+ [ExceptionGroup("", [RuntimeError()]), RuntimeError()],
526
+ )
527
+
528
+ with (
529
+ fails_raises_group(
530
+ "Raised exception group did not match: \n"
531
+ "2 matched exceptions. \n"
532
+ "The following expected exceptions did not find a match:\n"
533
+ " RaisesGroup(RuntimeError)\n"
534
+ " RaisesGroup(ValueError)\n"
535
+ "The following raised exceptions did not find a match\n"
536
+ " RuntimeError():\n"
537
+ # " 'RuntimeError' is not of type 'ValueError'\n"
538
+ # " Matcher(TypeError): 'RuntimeError' is not of type 'TypeError'\n"
539
+ " RaisesGroup(RuntimeError): 'RuntimeError' is not an exception group, but would match with `allow_unwrapped=True`\n"
540
+ " RaisesGroup(ValueError): 'RuntimeError' is not an exception group\n"
541
+ " ValueError('bar'):\n"
542
+ " It matches 'ValueError' which was paired with ValueError('foo')\n"
543
+ " RaisesGroup(RuntimeError): 'ValueError' is not an exception group\n"
544
+ " RaisesGroup(ValueError): 'ValueError' is not an exception group, but would match with `allow_unwrapped=True`",
545
+ add_prefix=False, # to see the full structure
546
+ ),
547
+ RaisesGroup(
548
+ ValueError,
549
+ Matcher(TypeError),
550
+ RaisesGroup(RuntimeError),
551
+ RaisesGroup(ValueError),
552
+ ),
553
+ ):
554
+ raise ExceptionGroup(
555
+ "a",
556
+ [RuntimeError(), TypeError(), ValueError("foo"), ValueError("bar")],
557
+ )
558
+
559
+ with (
560
+ fails_raises_group(
561
+ "1 matched exception. 'AssertionError' is not of type 'TypeError'"
562
+ ),
563
+ RaisesGroup(ValueError, TypeError),
564
+ ):
565
+ raise ExceptionGroup("a", [ValueError(), AssertionError()])
566
+
567
+ with (
568
+ fails_raises_group(
569
+ "Matcher(ValueError): 'TypeError' is not of type 'ValueError'"
570
+ ),
571
+ RaisesGroup(Matcher(ValueError)),
572
+ ):
573
+ raise ExceptionGroup("a", [TypeError()])
574
+
575
+ # suggest escaping
576
+ with (
577
+ fails_raises_group(
578
+ "Raised exception group did not match: Regex pattern 'h(ell)o' did not match 'h(ell)o' of 'ExceptionGroup'\n"
579
+ " Did you mean to `re.escape()` the regex?",
580
+ add_prefix=False, # to see the full structure
581
+ ),
582
+ RaisesGroup(ValueError, match="h(ell)o"),
583
+ ):
584
+ raise ExceptionGroup("h(ell)o", [ValueError()])
585
+ with (
586
+ fails_raises_group(
587
+ "Matcher(match='h(ell)o'): Regex pattern 'h(ell)o' did not match 'h(ell)o'\n"
588
+ " Did you mean to `re.escape()` the regex?",
589
+ ),
590
+ RaisesGroup(Matcher(match="h(ell)o")),
591
+ ):
592
+ raise ExceptionGroup("", [ValueError("h(ell)o")])
593
+
594
+ with (
595
+ fails_raises_group(
596
+ "Raised exception group did not match: \n"
597
+ "The following expected exceptions did not find a match:\n"
598
+ " 'ValueError'\n"
599
+ " 'ValueError'\n"
600
+ " 'ValueError'\n"
601
+ " 'ValueError'\n"
602
+ "The following raised exceptions did not find a match\n"
603
+ " ExceptionGroup('', [ValueError(), TypeError()]):\n"
604
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
605
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
606
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
607
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'",
608
+ add_prefix=False, # to see the full structure
609
+ ),
610
+ RaisesGroup(ValueError, ValueError, ValueError, ValueError),
611
+ ):
612
+ raise ExceptionGroup("", [ExceptionGroup("", [ValueError(), TypeError()])])
613
+
614
+
615
+ def test_message_indent() -> None:
616
+ with (
617
+ fails_raises_group(
618
+ "Raised exception group did not match: \n"
619
+ "The following expected exceptions did not find a match:\n"
620
+ " RaisesGroup(ValueError, ValueError)\n"
621
+ " 'ValueError'\n"
622
+ "The following raised exceptions did not find a match\n"
623
+ " ExceptionGroup('', [TypeError(), RuntimeError()]):\n"
624
+ " RaisesGroup(ValueError, ValueError): \n"
625
+ " The following expected exceptions did not find a match:\n"
626
+ " 'ValueError'\n"
627
+ " 'ValueError'\n"
628
+ " The following raised exceptions did not find a match\n"
629
+ " TypeError():\n"
630
+ " 'TypeError' is not of type 'ValueError'\n"
631
+ " 'TypeError' is not of type 'ValueError'\n"
632
+ " RuntimeError():\n"
633
+ " 'RuntimeError' is not of type 'ValueError'\n"
634
+ " 'RuntimeError' is not of type 'ValueError'\n"
635
+ # TODO: this line is not great, should maybe follow the same format as the other and say
636
+ # 'ValueError': Unexpected nested 'ExceptionGroup' (?)
637
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
638
+ " TypeError():\n"
639
+ " RaisesGroup(ValueError, ValueError): 'TypeError' is not an exception group\n"
640
+ " 'TypeError' is not of type 'ValueError'",
641
+ add_prefix=False,
642
+ ),
643
+ RaisesGroup(
644
+ RaisesGroup(ValueError, ValueError),
645
+ ValueError,
646
+ ),
647
+ ):
648
+ raise ExceptionGroup(
649
+ "",
650
+ [
651
+ ExceptionGroup("", [TypeError(), RuntimeError()]),
652
+ TypeError(),
653
+ ],
654
+ )
655
+ with (
656
+ fails_raises_group(
657
+ "Raised exception group did not match: \n"
658
+ "RaisesGroup(ValueError, ValueError): \n"
659
+ " The following expected exceptions did not find a match:\n"
660
+ " 'ValueError'\n"
661
+ " 'ValueError'\n"
662
+ " The following raised exceptions did not find a match\n"
663
+ " TypeError():\n"
664
+ " 'TypeError' is not of type 'ValueError'\n"
665
+ " 'TypeError' is not of type 'ValueError'\n"
666
+ " RuntimeError():\n"
667
+ " 'RuntimeError' is not of type 'ValueError'\n"
668
+ " 'RuntimeError' is not of type 'ValueError'",
669
+ add_prefix=False,
670
+ ),
671
+ RaisesGroup(
672
+ RaisesGroup(ValueError, ValueError),
673
+ ),
674
+ ):
675
+ raise ExceptionGroup(
676
+ "",
677
+ [
678
+ ExceptionGroup("", [TypeError(), RuntimeError()]),
679
+ ],
680
+ )
681
+
682
+
683
+ def test_suggestion_on_nested_and_brief_error() -> None:
684
+ # Make sure "Did you mean" suggestion gets indented iff it follows a single-line error
685
+ with (
686
+ fails_raises_group(
687
+ "\n"
688
+ "The following expected exceptions did not find a match:\n"
689
+ " RaisesGroup(ValueError)\n"
690
+ " 'ValueError'\n"
691
+ "The following raised exceptions did not find a match\n"
692
+ " ExceptionGroup('', [ExceptionGroup('', [ValueError()])]):\n"
693
+ " RaisesGroup(ValueError): Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
694
+ " Did you mean to use `flatten_subgroups=True`?\n"
695
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'",
696
+ ),
697
+ RaisesGroup(RaisesGroup(ValueError), ValueError),
698
+ ):
699
+ raise ExceptionGroup(
700
+ "",
701
+ [ExceptionGroup("", [ExceptionGroup("", [ValueError()])])],
702
+ )
703
+ # if indented here it would look like another raised exception
704
+ with (
705
+ fails_raises_group(
706
+ "\n"
707
+ "The following expected exceptions did not find a match:\n"
708
+ " RaisesGroup(ValueError, ValueError)\n"
709
+ " 'ValueError'\n"
710
+ "The following raised exceptions did not find a match\n"
711
+ " ExceptionGroup('', [ValueError(), ExceptionGroup('', [ValueError()])]):\n"
712
+ " RaisesGroup(ValueError, ValueError): \n"
713
+ " 1 matched exception. \n"
714
+ " The following expected exceptions did not find a match:\n"
715
+ " 'ValueError'\n"
716
+ " It matches ValueError() which was paired with 'ValueError'\n"
717
+ " The following raised exceptions did not find a match\n"
718
+ " ExceptionGroup('', [ValueError()]):\n"
719
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'\n"
720
+ " Did you mean to use `flatten_subgroups=True`?\n"
721
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'"
722
+ ),
723
+ RaisesGroup(RaisesGroup(ValueError, ValueError), ValueError),
724
+ ):
725
+ raise ExceptionGroup(
726
+ "",
727
+ [ExceptionGroup("", [ValueError(), ExceptionGroup("", [ValueError()])])],
728
+ )
729
+
730
+ # re.escape always comes after single-line errors
731
+ with (
732
+ fails_raises_group(
733
+ "\n"
734
+ "The following expected exceptions did not find a match:\n"
735
+ " RaisesGroup(Exception, match='^hello')\n"
736
+ " 'ValueError'\n"
737
+ "The following raised exceptions did not find a match\n"
738
+ " ExceptionGroup('^hello', [Exception()]):\n"
739
+ " RaisesGroup(Exception, match='^hello'): Regex pattern '^hello' did not match '^hello' of 'ExceptionGroup'\n"
740
+ " Did you mean to `re.escape()` the regex?\n"
741
+ " Unexpected nested 'ExceptionGroup', expected 'ValueError'"
742
+ ),
743
+ RaisesGroup(RaisesGroup(Exception, match="^hello"), ValueError),
744
+ ):
745
+ raise ExceptionGroup("", [ExceptionGroup("^hello", [Exception()])])
746
+
747
+
748
+ def test_assert_message_nested() -> None:
749
+ # we only get one instance of aaaaaaaaaa... and bbbbbb..., but we do get multiple instances of ccccc... and dddddd..
750
+ # but I think this now only prints the full repr when that is necessary to disambiguate exceptions
751
+ with (
752
+ fails_raises_group(
753
+ "Raised exception group did not match: \n"
754
+ "The following expected exceptions did not find a match:\n"
755
+ " RaisesGroup(ValueError)\n"
756
+ " RaisesGroup(RaisesGroup(ValueError))\n"
757
+ " RaisesGroup(Matcher(TypeError, match='foo'))\n"
758
+ " RaisesGroup(TypeError, ValueError)\n"
759
+ "The following raised exceptions did not find a match\n"
760
+ " TypeError('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'):\n"
761
+ " RaisesGroup(ValueError): 'TypeError' is not an exception group\n"
762
+ " RaisesGroup(RaisesGroup(ValueError)): 'TypeError' is not an exception group\n"
763
+ " RaisesGroup(Matcher(TypeError, match='foo')): 'TypeError' is not an exception group\n"
764
+ " RaisesGroup(TypeError, ValueError): 'TypeError' is not an exception group\n"
765
+ " ExceptionGroup('Exceptions from Trio nursery', [TypeError('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')]):\n"
766
+ " RaisesGroup(ValueError): 'TypeError' is not of type 'ValueError'\n"
767
+ " RaisesGroup(RaisesGroup(ValueError)): RaisesGroup(ValueError): 'TypeError' is not an exception group\n"
768
+ " RaisesGroup(Matcher(TypeError, match='foo')): Matcher(TypeError, match='foo'): Regex pattern 'foo' did not match 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\n"
769
+ " RaisesGroup(TypeError, ValueError): 1 matched exception. Too few exceptions raised, found no match for: ['ValueError']\n"
770
+ " ExceptionGroup('Exceptions from Trio nursery', [TypeError('cccccccccccccccccccccccccccccccccccccccc'), TypeError('dddddddddddddddddddddddddddddddddddddddd')]):\n"
771
+ " RaisesGroup(ValueError): \n"
772
+ " The following expected exceptions did not find a match:\n"
773
+ " 'ValueError'\n"
774
+ " The following raised exceptions did not find a match\n"
775
+ " TypeError('cccccccccccccccccccccccccccccccccccccccc'):\n"
776
+ " 'TypeError' is not of type 'ValueError'\n"
777
+ " TypeError('dddddddddddddddddddddddddddddddddddddddd'):\n"
778
+ " 'TypeError' is not of type 'ValueError'\n"
779
+ " RaisesGroup(RaisesGroup(ValueError)): \n"
780
+ " The following expected exceptions did not find a match:\n"
781
+ " RaisesGroup(ValueError)\n"
782
+ " The following raised exceptions did not find a match\n"
783
+ " TypeError('cccccccccccccccccccccccccccccccccccccccc'):\n"
784
+ " RaisesGroup(ValueError): 'TypeError' is not an exception group\n"
785
+ " TypeError('dddddddddddddddddddddddddddddddddddddddd'):\n"
786
+ " RaisesGroup(ValueError): 'TypeError' is not an exception group\n"
787
+ " RaisesGroup(Matcher(TypeError, match='foo')): \n"
788
+ " The following expected exceptions did not find a match:\n"
789
+ " Matcher(TypeError, match='foo')\n"
790
+ " The following raised exceptions did not find a match\n"
791
+ " TypeError('cccccccccccccccccccccccccccccccccccccccc'):\n"
792
+ " Matcher(TypeError, match='foo'): Regex pattern 'foo' did not match 'cccccccccccccccccccccccccccccccccccccccc'\n"
793
+ " TypeError('dddddddddddddddddddddddddddddddddddddddd'):\n"
794
+ " Matcher(TypeError, match='foo'): Regex pattern 'foo' did not match 'dddddddddddddddddddddddddddddddddddddddd'\n"
795
+ " RaisesGroup(TypeError, ValueError): \n"
796
+ " 1 matched exception. \n"
797
+ " The following expected exceptions did not find a match:\n"
798
+ " 'ValueError'\n"
799
+ " The following raised exceptions did not find a match\n"
800
+ " TypeError('dddddddddddddddddddddddddddddddddddddddd'):\n"
801
+ " It matches 'TypeError' which was paired with TypeError('cccccccccccccccccccccccccccccccccccccccc')\n"
802
+ " 'TypeError' is not of type 'ValueError'",
803
+ add_prefix=False, # to see the full structure
804
+ ),
805
+ RaisesGroup(
806
+ RaisesGroup(ValueError),
807
+ RaisesGroup(RaisesGroup(ValueError)),
808
+ RaisesGroup(Matcher(TypeError, match="foo")),
809
+ RaisesGroup(TypeError, ValueError),
810
+ ),
811
+ ):
812
+ raise ExceptionGroup(
813
+ "",
814
+ [
815
+ TypeError("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
816
+ ExceptionGroup(
817
+ "Exceptions from Trio nursery",
818
+ [TypeError("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")],
819
+ ),
820
+ ExceptionGroup(
821
+ "Exceptions from Trio nursery",
822
+ [
823
+ TypeError("cccccccccccccccccccccccccccccccccccccccc"),
824
+ TypeError("dddddddddddddddddddddddddddddddddddddddd"),
825
+ ],
826
+ ),
827
+ ],
828
+ )
829
+
830
+
831
+ @pytest.mark.skipif(
832
+ "hypothesis" in sys.modules,
833
+ reason="hypothesis may have monkeypatched _check_repr",
834
+ )
835
+ def test_check_no_patched_repr() -> None:
836
+ # We make `_check_repr` monkeypatchable to avoid this very ugly and verbose
837
+ # repr. The other tests that use `check` make use of `_check_repr` so they'll
838
+ # continue passing in case it is patched - but we have this one test that
839
+ # demonstrates just how nasty it gets otherwise.
840
+ with (
841
+ pytest.raises(
842
+ AssertionError,
843
+ match=(
844
+ r"^Raised exception group did not match: \n"
845
+ r"The following expected exceptions did not find a match:\n"
846
+ r" Matcher\(check=<function test_check_no_patched_repr.<locals>.<lambda> at .*>\)\n"
847
+ r" 'TypeError'\n"
848
+ r"The following raised exceptions did not find a match\n"
849
+ r" ValueError\('foo'\):\n"
850
+ r" Matcher\(check=<function test_check_no_patched_repr.<locals>.<lambda> at .*>\): check did not return True\n"
851
+ r" 'ValueError' is not of type 'TypeError'\n"
852
+ r" ValueError\('bar'\):\n"
853
+ r" Matcher\(check=<function test_check_no_patched_repr.<locals>.<lambda> at .*>\): check did not return True\n"
854
+ r" 'ValueError' is not of type 'TypeError'$"
855
+ ),
856
+ ),
857
+ RaisesGroup(Matcher(check=lambda x: False), TypeError),
858
+ ):
859
+ raise ExceptionGroup("", [ValueError("foo"), ValueError("bar")])
860
+
861
+
862
+ def test_misordering_example() -> None:
863
+ with (
864
+ fails_raises_group(
865
+ "\n"
866
+ "3 matched exceptions. \n"
867
+ "The following expected exceptions did not find a match:\n"
868
+ " Matcher(ValueError, match='foo')\n"
869
+ " It matches ValueError('foo') which was paired with 'ValueError'\n"
870
+ " It matches ValueError('foo') which was paired with 'ValueError'\n"
871
+ " It matches ValueError('foo') which was paired with 'ValueError'\n"
872
+ "The following raised exceptions did not find a match\n"
873
+ " ValueError('bar'):\n"
874
+ " It matches 'ValueError' which was paired with ValueError('foo')\n"
875
+ " It matches 'ValueError' which was paired with ValueError('foo')\n"
876
+ " It matches 'ValueError' which was paired with ValueError('foo')\n"
877
+ " Matcher(ValueError, match='foo'): Regex pattern 'foo' did not match 'bar'\n"
878
+ "There exist a possible match when attempting an exhaustive check, but RaisesGroup uses a greedy algorithm. Please make your expected exceptions more stringent with `Matcher` etc so the greedy algorithm can function."
879
+ ),
880
+ RaisesGroup(
881
+ ValueError, ValueError, ValueError, Matcher(ValueError, match="foo")
882
+ ),
883
+ ):
884
+ raise ExceptionGroup(
885
+ "",
886
+ [
887
+ ValueError("foo"),
888
+ ValueError("foo"),
889
+ ValueError("foo"),
890
+ ValueError("bar"),
891
+ ],
892
+ )
893
+
894
+
895
+ def test_brief_error_on_one_fail() -> None:
896
+ """if only one raised and one expected fail to match up, we print a full table iff
897
+ the raised exception would match one of the expected that previously got matched"""
898
+ # no also-matched
899
+ with (
900
+ fails_raises_group(
901
+ "1 matched exception. 'TypeError' is not of type 'RuntimeError'"
902
+ ),
903
+ RaisesGroup(ValueError, RuntimeError),
904
+ ):
905
+ raise ExceptionGroup("", [ValueError(), TypeError()])
906
+
907
+ # raised would match an expected
908
+ with (
909
+ fails_raises_group(
910
+ "\n"
911
+ "1 matched exception. \n"
912
+ "The following expected exceptions did not find a match:\n"
913
+ " 'RuntimeError'\n"
914
+ "The following raised exceptions did not find a match\n"
915
+ " TypeError():\n"
916
+ " It matches 'Exception' which was paired with ValueError()\n"
917
+ " 'TypeError' is not of type 'RuntimeError'"
918
+ ),
919
+ RaisesGroup(Exception, RuntimeError),
920
+ ):
921
+ raise ExceptionGroup("", [ValueError(), TypeError()])
922
+
923
+ # expected would match a raised
924
+ with (
925
+ fails_raises_group(
926
+ "\n"
927
+ "1 matched exception. \n"
928
+ "The following expected exceptions did not find a match:\n"
929
+ " 'ValueError'\n"
930
+ " It matches ValueError() which was paired with 'ValueError'\n"
931
+ "The following raised exceptions did not find a match\n"
932
+ " TypeError():\n"
933
+ " 'TypeError' is not of type 'ValueError'"
934
+ ),
935
+ RaisesGroup(ValueError, ValueError),
936
+ ):
937
+ raise ExceptionGroup("", [ValueError(), TypeError()])
938
+
939
+
940
+ def test_identity_oopsies() -> None:
941
+ # it's both possible to have several instances of the same exception in the same group
942
+ # and to expect multiple of the same type
943
+ # this previously messed up the logic
944
+
945
+ with (
946
+ fails_raises_group(
947
+ "3 matched exceptions. 'RuntimeError' is not of type 'TypeError'"
948
+ ),
949
+ RaisesGroup(ValueError, ValueError, ValueError, TypeError),
950
+ ):
951
+ raise ExceptionGroup(
952
+ "", [ValueError(), ValueError(), ValueError(), RuntimeError()]
953
+ )
954
+
955
+ e = ValueError("foo")
956
+ m = Matcher(match="bar")
957
+ with (
958
+ fails_raises_group(
959
+ "\n"
960
+ "The following expected exceptions did not find a match:\n"
961
+ " Matcher(match='bar')\n"
962
+ " Matcher(match='bar')\n"
963
+ " Matcher(match='bar')\n"
964
+ "The following raised exceptions did not find a match\n"
965
+ " ValueError('foo'):\n"
966
+ " Matcher(match='bar'): Regex pattern 'bar' did not match 'foo'\n"
967
+ " Matcher(match='bar'): Regex pattern 'bar' did not match 'foo'\n"
968
+ " Matcher(match='bar'): Regex pattern 'bar' did not match 'foo'\n"
969
+ " ValueError('foo'):\n"
970
+ " Matcher(match='bar'): Regex pattern 'bar' did not match 'foo'\n"
971
+ " Matcher(match='bar'): Regex pattern 'bar' did not match 'foo'\n"
972
+ " Matcher(match='bar'): Regex pattern 'bar' did not match 'foo'\n"
973
+ " ValueError('foo'):\n"
974
+ " Matcher(match='bar'): Regex pattern 'bar' did not match 'foo'\n"
975
+ " Matcher(match='bar'): Regex pattern 'bar' did not match 'foo'\n"
976
+ " Matcher(match='bar'): Regex pattern 'bar' did not match 'foo'"
977
+ ),
978
+ RaisesGroup(m, m, m),
979
+ ):
980
+ raise ExceptionGroup("", [e, e, e])
981
+
982
+
983
+ def test_matcher() -> None:
984
+ with pytest.raises(
985
+ ValueError,
986
+ match=r"^You must specify at least one parameter to match on.$",
987
+ ):
988
+ Matcher() # type: ignore[call-overload]
989
+ with pytest.raises(
990
+ ValueError,
991
+ match=f"^exception_type {re.escape(repr(object))} must be a subclass of BaseException$",
992
+ ):
993
+ Matcher(object) # type: ignore[type-var]
994
+
995
+ with RaisesGroup(Matcher(ValueError)):
996
+ raise ExceptionGroup("", (ValueError(),))
997
+ with (
998
+ fails_raises_group(
999
+ "Matcher(TypeError): 'ValueError' is not of type 'TypeError'"
1000
+ ),
1001
+ RaisesGroup(Matcher(TypeError)),
1002
+ ):
1003
+ raise ExceptionGroup("", (ValueError(),))
1004
+
1005
+
1006
+ def test_matcher_match() -> None:
1007
+ with RaisesGroup(Matcher(ValueError, "foo")):
1008
+ raise ExceptionGroup("", (ValueError("foo"),))
1009
+ with (
1010
+ fails_raises_group(
1011
+ "Matcher(ValueError, match='foo'): Regex pattern 'foo' did not match 'bar'"
1012
+ ),
1013
+ RaisesGroup(Matcher(ValueError, "foo")),
1014
+ ):
1015
+ raise ExceptionGroup("", (ValueError("bar"),))
1016
+
1017
+ # Can be used without specifying the type
1018
+ with RaisesGroup(Matcher(match="foo")):
1019
+ raise ExceptionGroup("", (ValueError("foo"),))
1020
+ with (
1021
+ fails_raises_group(
1022
+ "Matcher(match='foo'): Regex pattern 'foo' did not match 'bar'"
1023
+ ),
1024
+ RaisesGroup(Matcher(match="foo")),
1025
+ ):
1026
+ raise ExceptionGroup("", (ValueError("bar"),))
1027
+
1028
+ # check ^$
1029
+ with RaisesGroup(Matcher(ValueError, match="^bar$")):
1030
+ raise ExceptionGroup("", [ValueError("bar")])
1031
+ with (
1032
+ fails_raises_group(
1033
+ "Matcher(ValueError, match='^bar$'): Regex pattern '^bar$' did not match 'barr'"
1034
+ ),
1035
+ RaisesGroup(Matcher(ValueError, match="^bar$")),
1036
+ ):
1037
+ raise ExceptionGroup("", [ValueError("barr")])
1038
+
1039
+
1040
+ def test_Matcher_check() -> None:
1041
+ def check_oserror_and_errno_is_5(e: BaseException) -> bool:
1042
+ return isinstance(e, OSError) and e.errno == 5
1043
+
1044
+ with RaisesGroup(Matcher(check=check_oserror_and_errno_is_5)):
1045
+ raise ExceptionGroup("", (OSError(5, ""),))
1046
+
1047
+ # specifying exception_type narrows the parameter type to the callable
1048
+ def check_errno_is_5(e: OSError) -> bool:
1049
+ return e.errno == 5
1050
+
1051
+ with RaisesGroup(Matcher(OSError, check=check_errno_is_5)):
1052
+ raise ExceptionGroup("", (OSError(5, ""),))
1053
+
1054
+ # avoid printing overly verbose repr multiple times
1055
+ with (
1056
+ fails_raises_group(
1057
+ f"Matcher(OSError, check={check_errno_is_5!r}): check did not return True"
1058
+ ),
1059
+ RaisesGroup(Matcher(OSError, check=check_errno_is_5)),
1060
+ ):
1061
+ raise ExceptionGroup("", (OSError(6, ""),))
1062
+
1063
+ # in nested cases you still get it multiple times though
1064
+ # to address this you'd need logic in Matcher.__repr__ and RaisesGroup.__repr__
1065
+ with (
1066
+ fails_raises_group(
1067
+ f"RaisesGroup(Matcher(OSError, check={check_errno_is_5!r})): Matcher(OSError, check={check_errno_is_5!r}): check did not return True"
1068
+ ),
1069
+ RaisesGroup(RaisesGroup(Matcher(OSError, check=check_errno_is_5))),
1070
+ ):
1071
+ raise ExceptionGroup("", [ExceptionGroup("", [OSError(6, "")])])
1072
+
1073
+
1074
+ def test_matcher_tostring() -> None:
1075
+ assert str(Matcher(ValueError)) == "Matcher(ValueError)"
1076
+ assert str(Matcher(match="[a-z]")) == "Matcher(match='[a-z]')"
1077
+ pattern_no_flags = re.compile(r"noflag", 0)
1078
+ assert str(Matcher(match=pattern_no_flags)) == "Matcher(match='noflag')"
1079
+ pattern_flags = re.compile(r"noflag", re.IGNORECASE)
1080
+ assert str(Matcher(match=pattern_flags)) == f"Matcher(match={pattern_flags!r})"
1081
+ assert (
1082
+ str(Matcher(ValueError, match="re", check=bool))
1083
+ == f"Matcher(ValueError, match='re', check={bool!r})"
1084
+ )
1085
+
1086
+
1087
+ def test_raisesgroup_tostring() -> None:
1088
+ def check_str_and_repr(s: str) -> None:
1089
+ evaled = eval(s)
1090
+ assert s == str(evaled) == repr(evaled)
1091
+
1092
+ check_str_and_repr("RaisesGroup(ValueError)")
1093
+ check_str_and_repr("RaisesGroup(RaisesGroup(ValueError))")
1094
+ check_str_and_repr("RaisesGroup(Matcher(ValueError))")
1095
+ check_str_and_repr("RaisesGroup(ValueError, allow_unwrapped=True)")
1096
+ check_str_and_repr("RaisesGroup(ValueError, match='aoeu')")
1097
+
1098
+ assert (
1099
+ str(RaisesGroup(ValueError, match="[a-z]", check=bool))
1100
+ == f"RaisesGroup(ValueError, match='[a-z]', check={bool!r})"
1101
+ )
1102
+
1103
+
1104
+ def test__ExceptionInfo(monkeypatch: pytest.MonkeyPatch) -> None:
1105
+ monkeypatch.setattr(
1106
+ trio.testing._raises_group,
1107
+ "ExceptionInfo",
1108
+ trio.testing._raises_group._ExceptionInfo,
1109
+ )
1110
+ with RaisesGroup(ValueError) as excinfo:
1111
+ raise ExceptionGroup("", (ValueError("hello"),))
1112
+ assert excinfo.type is ExceptionGroup
1113
+ assert excinfo.value.exceptions[0].args == ("hello",)
1114
+ assert isinstance(excinfo.tb, TracebackType)
1115
+
1116
+
1117
+ def test_raisesgroup_matcher_deprecation() -> None:
1118
+ with pytest.deprecated_call():
1119
+ trio.testing.Matcher # type: ignore # noqa: B018
1120
+
1121
+ with pytest.deprecated_call():
1122
+ trio.testing.RaisesGroup # type: ignore # noqa: B018
1123
+
1124
+ with pytest.deprecated_call():
1125
+ from trio.testing import Matcher # type: ignore # noqa: F401
1126
+
1127
+ with pytest.deprecated_call():
1128
+ from trio.testing import RaisesGroup # type: ignore # noqa: F401