superacli 1.1.4 → 1.1.6

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 (961) hide show
  1. package/__tests__/adapter-schema.test.js +2 -0
  2. package/__tests__/config.test.js +62 -1
  3. package/__tests__/help-json.test.js +2 -0
  4. package/__tests__/mcp-adapter.test.js +14 -4
  5. package/__tests__/mcp-local.test.js +159 -0
  6. package/__tests__/mcp-stdio-jsonrpc.test.js +105 -0
  7. package/__tests__/monty-plugin.test.js +121 -0
  8. package/__tests__/plugin-browser-use-uninstall.test.js +23 -0
  9. package/__tests__/plugin-browser-use.test.js +77 -0
  10. package/__tests__/plugins-command.test.js +92 -1
  11. package/__tests__/plugins-learn.test.js +62 -0
  12. package/__tests__/plugins-registry.test.js +3 -1
  13. package/__tests__/resend-plugin.test.js +122 -0
  14. package/__tests__/skills.test.js +4 -0
  15. package/cli/adapter-schema.js +3 -2
  16. package/cli/adapters/mcp.js +22 -3
  17. package/cli/adapters/process.js +34 -7
  18. package/cli/config.js +27 -1
  19. package/cli/help-json.js +2 -2
  20. package/cli/mcp-diagnostics.js +152 -0
  21. package/cli/mcp-discovery.js +221 -0
  22. package/cli/mcp-local.js +267 -25
  23. package/cli/mcp-stdio-jsonrpc.js +246 -0
  24. package/cli/plugin-install-guidance.js +25 -0
  25. package/cli/plugins-command.js +86 -3
  26. package/cli/plugins-learn.js +177 -0
  27. package/cli/plugins-manager.js +3 -0
  28. package/cli/plugins-registry.js +2 -1
  29. package/cli/skills-mcp.js +102 -0
  30. package/cli/skills.js +6 -40
  31. package/cli/supercli.js +7 -2
  32. package/docs/initial/mcp-local-mode.md +35 -0
  33. package/docs/mcp-cheatsheet.md +324 -0
  34. package/docs/plugins.md +7 -0
  35. package/package.json +1 -1
  36. package/plugins/browser-use/plugin.json +23 -0
  37. package/plugins/browser-use/scripts/post-install.js +146 -0
  38. package/plugins/browser-use/scripts/post-uninstall.js +28 -0
  39. package/plugins/browser-use/skills/quickstart/SKILL.md +47 -0
  40. package/plugins/monty/README.md +49 -0
  41. package/plugins/monty/plugin.json +69 -0
  42. package/plugins/monty/scripts/post-install.js +73 -0
  43. package/plugins/monty/scripts/post-uninstall.js +23 -0
  44. package/plugins/monty/scripts/run-python.js +140 -0
  45. package/plugins/monty/scripts/setup-monty.js +27 -0
  46. package/plugins/plugins.json +29 -0
  47. package/plugins/resend/plugin.json +371 -0
  48. package/plugins/resend/scripts/post-install.js +59 -0
  49. package/plugins/resend/scripts/post-uninstall.js +23 -0
  50. package/plugins/resend/scripts/setup-resend.js +27 -0
  51. package/plugins/resend/skills/quickstart/SKILL.md +80 -0
  52. package/ref-monty/.cargo/config.toml +3 -0
  53. package/ref-monty/.claude/settings.json +60 -0
  54. package/ref-monty/.claude/skills/fastmod/SKILL.md +22 -0
  55. package/ref-monty/.claude/skills/python-playground/SKILL.md +47 -0
  56. package/ref-monty/.codecov.yml +12 -0
  57. package/ref-monty/.github/actions/build-pgo-wheel/action.yml +72 -0
  58. package/ref-monty/.github/workflows/ci.yml +776 -0
  59. package/ref-monty/.github/workflows/codspeed.yml +45 -0
  60. package/ref-monty/.github/workflows/init-npm-packages.yml +82 -0
  61. package/ref-monty/.pre-commit-config.yaml +47 -0
  62. package/ref-monty/.python-version +1 -0
  63. package/ref-monty/.rustfmt.toml +4 -0
  64. package/ref-monty/.zed/settings.json +11 -0
  65. package/ref-monty/CLAUDE.md +535 -0
  66. package/ref-monty/Cargo.lock +3798 -0
  67. package/ref-monty/Cargo.toml +87 -0
  68. package/ref-monty/LICENSE +21 -0
  69. package/ref-monty/Makefile +216 -0
  70. package/ref-monty/README.md +430 -0
  71. package/ref-monty/RELEASING.md +47 -0
  72. package/ref-monty/crates/fuzz/Cargo.toml +30 -0
  73. package/ref-monty/crates/fuzz/fuzz_targets/string_input_panic.rs +37 -0
  74. package/ref-monty/crates/fuzz/fuzz_targets/tokens_input_panic.rs +552 -0
  75. package/ref-monty/crates/monty/Cargo.toml +68 -0
  76. package/ref-monty/crates/monty/benches/main.rs +247 -0
  77. package/ref-monty/crates/monty/build.rs +10 -0
  78. package/ref-monty/crates/monty/src/args.rs +733 -0
  79. package/ref-monty/crates/monty/src/asyncio.rs +179 -0
  80. package/ref-monty/crates/monty/src/builtins/abs.rs +55 -0
  81. package/ref-monty/crates/monty/src/builtins/all.rs +30 -0
  82. package/ref-monty/crates/monty/src/builtins/any.rs +30 -0
  83. package/ref-monty/crates/monty/src/builtins/bin.rs +59 -0
  84. package/ref-monty/crates/monty/src/builtins/chr.rs +46 -0
  85. package/ref-monty/crates/monty/src/builtins/divmod.rs +164 -0
  86. package/ref-monty/crates/monty/src/builtins/enumerate.rs +52 -0
  87. package/ref-monty/crates/monty/src/builtins/filter.rs +67 -0
  88. package/ref-monty/crates/monty/src/builtins/getattr.rs +65 -0
  89. package/ref-monty/crates/monty/src/builtins/hash.rs +28 -0
  90. package/ref-monty/crates/monty/src/builtins/hex.rs +58 -0
  91. package/ref-monty/crates/monty/src/builtins/id.rs +24 -0
  92. package/ref-monty/crates/monty/src/builtins/isinstance.rs +68 -0
  93. package/ref-monty/crates/monty/src/builtins/len.rs +25 -0
  94. package/ref-monty/crates/monty/src/builtins/map.rs +98 -0
  95. package/ref-monty/crates/monty/src/builtins/min_max.rs +113 -0
  96. package/ref-monty/crates/monty/src/builtins/mod.rs +246 -0
  97. package/ref-monty/crates/monty/src/builtins/next.rs +21 -0
  98. package/ref-monty/crates/monty/src/builtins/oct.rs +59 -0
  99. package/ref-monty/crates/monty/src/builtins/ord.rs +67 -0
  100. package/ref-monty/crates/monty/src/builtins/pow.rs +365 -0
  101. package/ref-monty/crates/monty/src/builtins/print.rs +141 -0
  102. package/ref-monty/crates/monty/src/builtins/repr.rs +16 -0
  103. package/ref-monty/crates/monty/src/builtins/reversed.rs +28 -0
  104. package/ref-monty/crates/monty/src/builtins/round.rs +174 -0
  105. package/ref-monty/crates/monty/src/builtins/sorted.rs +151 -0
  106. package/ref-monty/crates/monty/src/builtins/sum.rs +66 -0
  107. package/ref-monty/crates/monty/src/builtins/type_.rs +16 -0
  108. package/ref-monty/crates/monty/src/builtins/zip.rs +77 -0
  109. package/ref-monty/crates/monty/src/bytecode/builder.rs +699 -0
  110. package/ref-monty/crates/monty/src/bytecode/code.rs +310 -0
  111. package/ref-monty/crates/monty/src/bytecode/compiler.rs +3206 -0
  112. package/ref-monty/crates/monty/src/bytecode/mod.rs +24 -0
  113. package/ref-monty/crates/monty/src/bytecode/op.rs +617 -0
  114. package/ref-monty/crates/monty/src/bytecode/vm/async_exec.rs +1058 -0
  115. package/ref-monty/crates/monty/src/bytecode/vm/attr.rs +63 -0
  116. package/ref-monty/crates/monty/src/bytecode/vm/binary.rs +487 -0
  117. package/ref-monty/crates/monty/src/bytecode/vm/call.rs +767 -0
  118. package/ref-monty/crates/monty/src/bytecode/vm/collections.rs +741 -0
  119. package/ref-monty/crates/monty/src/bytecode/vm/compare.rs +147 -0
  120. package/ref-monty/crates/monty/src/bytecode/vm/exceptions.rs +297 -0
  121. package/ref-monty/crates/monty/src/bytecode/vm/format.rs +132 -0
  122. package/ref-monty/crates/monty/src/bytecode/vm/mod.rs +1958 -0
  123. package/ref-monty/crates/monty/src/bytecode/vm/scheduler.rs +620 -0
  124. package/ref-monty/crates/monty/src/exception_private.rs +1513 -0
  125. package/ref-monty/crates/monty/src/exception_public.rs +346 -0
  126. package/ref-monty/crates/monty/src/expressions.rs +694 -0
  127. package/ref-monty/crates/monty/src/fstring.rs +854 -0
  128. package/ref-monty/crates/monty/src/function.rs +119 -0
  129. package/ref-monty/crates/monty/src/heap.rs +1073 -0
  130. package/ref-monty/crates/monty/src/heap_data.rs +985 -0
  131. package/ref-monty/crates/monty/src/heap_traits.rs +312 -0
  132. package/ref-monty/crates/monty/src/intern.rs +837 -0
  133. package/ref-monty/crates/monty/src/io.rs +106 -0
  134. package/ref-monty/crates/monty/src/lib.rs +52 -0
  135. package/ref-monty/crates/monty/src/modules/asyncio.rs +144 -0
  136. package/ref-monty/crates/monty/src/modules/math.rs +1453 -0
  137. package/ref-monty/crates/monty/src/modules/mod.rs +120 -0
  138. package/ref-monty/crates/monty/src/modules/os.rs +116 -0
  139. package/ref-monty/crates/monty/src/modules/pathlib.rs +33 -0
  140. package/ref-monty/crates/monty/src/modules/re.rs +606 -0
  141. package/ref-monty/crates/monty/src/modules/sys.rs +60 -0
  142. package/ref-monty/crates/monty/src/modules/typing.rs +70 -0
  143. package/ref-monty/crates/monty/src/namespace.rs +21 -0
  144. package/ref-monty/crates/monty/src/object.rs +1040 -0
  145. package/ref-monty/crates/monty/src/os.rs +215 -0
  146. package/ref-monty/crates/monty/src/parse.rs +1730 -0
  147. package/ref-monty/crates/monty/src/prepare.rs +3015 -0
  148. package/ref-monty/crates/monty/src/repl.rs +1109 -0
  149. package/ref-monty/crates/monty/src/resource.rs +559 -0
  150. package/ref-monty/crates/monty/src/run.rs +457 -0
  151. package/ref-monty/crates/monty/src/run_progress.rs +821 -0
  152. package/ref-monty/crates/monty/src/signature.rs +651 -0
  153. package/ref-monty/crates/monty/src/sorting.rs +100 -0
  154. package/ref-monty/crates/monty/src/types/bytes.rs +2356 -0
  155. package/ref-monty/crates/monty/src/types/dataclass.rs +345 -0
  156. package/ref-monty/crates/monty/src/types/dict.rs +879 -0
  157. package/ref-monty/crates/monty/src/types/dict_view.rs +619 -0
  158. package/ref-monty/crates/monty/src/types/iter.rs +799 -0
  159. package/ref-monty/crates/monty/src/types/list.rs +929 -0
  160. package/ref-monty/crates/monty/src/types/long_int.rs +211 -0
  161. package/ref-monty/crates/monty/src/types/mod.rs +48 -0
  162. package/ref-monty/crates/monty/src/types/module.rs +146 -0
  163. package/ref-monty/crates/monty/src/types/namedtuple.rs +261 -0
  164. package/ref-monty/crates/monty/src/types/path.rs +596 -0
  165. package/ref-monty/crates/monty/src/types/property.rs +35 -0
  166. package/ref-monty/crates/monty/src/types/py_trait.rs +322 -0
  167. package/ref-monty/crates/monty/src/types/range.rs +285 -0
  168. package/ref-monty/crates/monty/src/types/re_match.rs +522 -0
  169. package/ref-monty/crates/monty/src/types/re_pattern.rs +726 -0
  170. package/ref-monty/crates/monty/src/types/set.rs +1373 -0
  171. package/ref-monty/crates/monty/src/types/slice.rs +257 -0
  172. package/ref-monty/crates/monty/src/types/str.rs +2051 -0
  173. package/ref-monty/crates/monty/src/types/tuple.rs +376 -0
  174. package/ref-monty/crates/monty/src/types/type.rs +407 -0
  175. package/ref-monty/crates/monty/src/value.rs +2558 -0
  176. package/ref-monty/crates/monty/test_cases/args__dict_get_no_args.py +3 -0
  177. package/ref-monty/crates/monty/test_cases/args__dict_get_too_many.py +3 -0
  178. package/ref-monty/crates/monty/test_cases/args__dict_items_with_args.py +3 -0
  179. package/ref-monty/crates/monty/test_cases/args__dict_keys_with_args.py +3 -0
  180. package/ref-monty/crates/monty/test_cases/args__dict_pop_no_args.py +3 -0
  181. package/ref-monty/crates/monty/test_cases/args__dict_pop_too_many.py +3 -0
  182. package/ref-monty/crates/monty/test_cases/args__dict_values_with_args.py +3 -0
  183. package/ref-monty/crates/monty/test_cases/args__id_too_many.py +2 -0
  184. package/ref-monty/crates/monty/test_cases/args__len_no_args.py +2 -0
  185. package/ref-monty/crates/monty/test_cases/args__len_too_many.py +2 -0
  186. package/ref-monty/crates/monty/test_cases/args__len_type_error_int.py +9 -0
  187. package/ref-monty/crates/monty/test_cases/args__len_type_error_none.py +9 -0
  188. package/ref-monty/crates/monty/test_cases/args__list_append_no_args.py +3 -0
  189. package/ref-monty/crates/monty/test_cases/args__list_append_too_many.py +3 -0
  190. package/ref-monty/crates/monty/test_cases/args__list_insert_too_few.py +3 -0
  191. package/ref-monty/crates/monty/test_cases/args__list_insert_too_many.py +3 -0
  192. package/ref-monty/crates/monty/test_cases/args__repr_no_args.py +2 -0
  193. package/ref-monty/crates/monty/test_cases/arith__div_zero_float.py +2 -0
  194. package/ref-monty/crates/monty/test_cases/arith__div_zero_int.py +2 -0
  195. package/ref-monty/crates/monty/test_cases/arith__floordiv_zero_float.py +2 -0
  196. package/ref-monty/crates/monty/test_cases/arith__floordiv_zero_int.py +2 -0
  197. package/ref-monty/crates/monty/test_cases/arith__pow_zero_neg.py +2 -0
  198. package/ref-monty/crates/monty/test_cases/arith__pow_zero_neg_builtin.py +9 -0
  199. package/ref-monty/crates/monty/test_cases/assert__expr_fail.py +2 -0
  200. package/ref-monty/crates/monty/test_cases/assert__fail.py +2 -0
  201. package/ref-monty/crates/monty/test_cases/assert__fail_msg.py +2 -0
  202. package/ref-monty/crates/monty/test_cases/assert__fn_fail.py +3 -0
  203. package/ref-monty/crates/monty/test_cases/assert__ops.py +11 -0
  204. package/ref-monty/crates/monty/test_cases/async__asyncio_run.py +47 -0
  205. package/ref-monty/crates/monty/test_cases/async__basic.py +10 -0
  206. package/ref-monty/crates/monty/test_cases/async__closure.py +14 -0
  207. package/ref-monty/crates/monty/test_cases/async__double_await_coroutine.py +16 -0
  208. package/ref-monty/crates/monty/test_cases/async__exception.py +10 -0
  209. package/ref-monty/crates/monty/test_cases/async__ext_call.py +73 -0
  210. package/ref-monty/crates/monty/test_cases/async__gather_all.py +85 -0
  211. package/ref-monty/crates/monty/test_cases/async__nested_await.py +15 -0
  212. package/ref-monty/crates/monty/test_cases/async__nested_gather_ext.py +37 -0
  213. package/ref-monty/crates/monty/test_cases/async__not_awaitable.py +10 -0
  214. package/ref-monty/crates/monty/test_cases/async__not_imported.py +14 -0
  215. package/ref-monty/crates/monty/test_cases/async__recursion_depth_isolation.py +27 -0
  216. package/ref-monty/crates/monty/test_cases/async__return_types.py +31 -0
  217. package/ref-monty/crates/monty/test_cases/async__sequential.py +16 -0
  218. package/ref-monty/crates/monty/test_cases/async__traceback.py +19 -0
  219. package/ref-monty/crates/monty/test_cases/async__with_args.py +14 -0
  220. package/ref-monty/crates/monty/test_cases/attr__get_int_error.py +9 -0
  221. package/ref-monty/crates/monty/test_cases/attr__get_list_error.py +9 -0
  222. package/ref-monty/crates/monty/test_cases/attr__set_frozen_nonfield.py +12 -0
  223. package/ref-monty/crates/monty/test_cases/attr__set_int_error.py +10 -0
  224. package/ref-monty/crates/monty/test_cases/attr__set_list_error.py +10 -0
  225. package/ref-monty/crates/monty/test_cases/bench__kitchen_sink.py +68 -0
  226. package/ref-monty/crates/monty/test_cases/bool__ops.py +20 -0
  227. package/ref-monty/crates/monty/test_cases/builtin__add_type_error.py +2 -0
  228. package/ref-monty/crates/monty/test_cases/builtin__filter.py +62 -0
  229. package/ref-monty/crates/monty/test_cases/builtin__filter_not_iterable.py +11 -0
  230. package/ref-monty/crates/monty/test_cases/builtin__getattr.py +84 -0
  231. package/ref-monty/crates/monty/test_cases/builtin__iter_funcs.py +42 -0
  232. package/ref-monty/crates/monty/test_cases/builtin__iter_next.py +66 -0
  233. package/ref-monty/crates/monty/test_cases/builtin__map.py +74 -0
  234. package/ref-monty/crates/monty/test_cases/builtin__map_not_iterable.py +11 -0
  235. package/ref-monty/crates/monty/test_cases/builtin__math_funcs.py +154 -0
  236. package/ref-monty/crates/monty/test_cases/builtin__more_iter_funcs.py +148 -0
  237. package/ref-monty/crates/monty/test_cases/builtin__next_stop_iteration.py +10 -0
  238. package/ref-monty/crates/monty/test_cases/builtin__print_invalid_kwarg.py +9 -0
  239. package/ref-monty/crates/monty/test_cases/builtin__print_kwargs.py +12 -0
  240. package/ref-monty/crates/monty/test_cases/builtin__repr.py +3 -0
  241. package/ref-monty/crates/monty/test_cases/builtin__string_funcs.py +73 -0
  242. package/ref-monty/crates/monty/test_cases/bytes__decode_invalid_utf8.py +18 -0
  243. package/ref-monty/crates/monty/test_cases/bytes__endswith_str_error.py +10 -0
  244. package/ref-monty/crates/monty/test_cases/bytes__getitem_index_error.py +10 -0
  245. package/ref-monty/crates/monty/test_cases/bytes__index_start_gt_end.py +10 -0
  246. package/ref-monty/crates/monty/test_cases/bytes__methods.py +394 -0
  247. package/ref-monty/crates/monty/test_cases/bytes__negative_count.py +9 -0
  248. package/ref-monty/crates/monty/test_cases/bytes__ops.py +90 -0
  249. package/ref-monty/crates/monty/test_cases/bytes__startswith_str_error.py +10 -0
  250. package/ref-monty/crates/monty/test_cases/call_object.py +3 -0
  251. package/ref-monty/crates/monty/test_cases/chain_comparison__all.py +79 -0
  252. package/ref-monty/crates/monty/test_cases/closure__param_shadows_outer.py +81 -0
  253. package/ref-monty/crates/monty/test_cases/closure__pep448.py +203 -0
  254. package/ref-monty/crates/monty/test_cases/closure__undefined_nonlocal.py +13 -0
  255. package/ref-monty/crates/monty/test_cases/compare__mixed_types.py +120 -0
  256. package/ref-monty/crates/monty/test_cases/comprehension__all.py +208 -0
  257. package/ref-monty/crates/monty/test_cases/comprehension__scope.py +7 -0
  258. package/ref-monty/crates/monty/test_cases/comprehension__unbound_local.py +14 -0
  259. package/ref-monty/crates/monty/test_cases/dataclass__basic.py +238 -0
  260. package/ref-monty/crates/monty/test_cases/dataclass__call_field_error.py +12 -0
  261. package/ref-monty/crates/monty/test_cases/dataclass__frozen_set_error.py +12 -0
  262. package/ref-monty/crates/monty/test_cases/dataclass__get_missing_attr_error.py +11 -0
  263. package/ref-monty/crates/monty/test_cases/dict__get_unhashable_key.py +3 -0
  264. package/ref-monty/crates/monty/test_cases/dict__literal_unhashable_key.py +2 -0
  265. package/ref-monty/crates/monty/test_cases/dict__method_pop_missing_error.py +3 -0
  266. package/ref-monty/crates/monty/test_cases/dict__methods.py +151 -0
  267. package/ref-monty/crates/monty/test_cases/dict__ops.py +133 -0
  268. package/ref-monty/crates/monty/test_cases/dict__pop_unhashable_key.py +4 -0
  269. package/ref-monty/crates/monty/test_cases/dict__popitem_empty.py +9 -0
  270. package/ref-monty/crates/monty/test_cases/dict__subscript_missing_key.py +3 -0
  271. package/ref-monty/crates/monty/test_cases/dict__unhashable_dict_key.py +2 -0
  272. package/ref-monty/crates/monty/test_cases/dict__unhashable_list_key.py +2 -0
  273. package/ref-monty/crates/monty/test_cases/dict__unpack_type_error.py +2 -0
  274. package/ref-monty/crates/monty/test_cases/dict__views.py +165 -0
  275. package/ref-monty/crates/monty/test_cases/edge__all.py +26 -0
  276. package/ref-monty/crates/monty/test_cases/edge__float_int_mod.py +2 -0
  277. package/ref-monty/crates/monty/test_cases/edge__int_float_mod.py +2 -0
  278. package/ref-monty/crates/monty/test_cases/exc__args.py +16 -0
  279. package/ref-monty/crates/monty/test_cases/exc__str.py +15 -0
  280. package/ref-monty/crates/monty/test_cases/execute_ok__all.py +54 -0
  281. package/ref-monty/crates/monty/test_cases/execute_raise__error_instance_str.py +2 -0
  282. package/ref-monty/crates/monty/test_cases/execute_raise__error_no_args.py +2 -0
  283. package/ref-monty/crates/monty/test_cases/execute_raise__error_string_arg.py +2 -0
  284. package/ref-monty/crates/monty/test_cases/execute_raise__error_string_arg_quotes.py +2 -0
  285. package/ref-monty/crates/monty/test_cases/execute_raise__error_type.py +2 -0
  286. package/ref-monty/crates/monty/test_cases/execute_raise__raise_instance_via_var.py +4 -0
  287. package/ref-monty/crates/monty/test_cases/execute_raise__raise_list.py +2 -0
  288. package/ref-monty/crates/monty/test_cases/execute_raise__raise_number.py +2 -0
  289. package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_call_via_var.py +4 -0
  290. package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_direct.py +3 -0
  291. package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_via_var.py +4 -0
  292. package/ref-monty/crates/monty/test_cases/ext_call__arg_side_effect_bug.py +22 -0
  293. package/ref-monty/crates/monty/test_cases/ext_call__augmented.py +17 -0
  294. package/ref-monty/crates/monty/test_cases/ext_call__augmented_refcount_bug.py +7 -0
  295. package/ref-monty/crates/monty/test_cases/ext_call__bare_raise_after_resume.py +34 -0
  296. package/ref-monty/crates/monty/test_cases/ext_call__basic.py +99 -0
  297. package/ref-monty/crates/monty/test_cases/ext_call__boolean.py +37 -0
  298. package/ref-monty/crates/monty/test_cases/ext_call__boolean_side_effect_hang.py +17 -0
  299. package/ref-monty/crates/monty/test_cases/ext_call__closure_bug.py +16 -0
  300. package/ref-monty/crates/monty/test_cases/ext_call__comparison.py +26 -0
  301. package/ref-monty/crates/monty/test_cases/ext_call__deep_call_stack.py +18 -0
  302. package/ref-monty/crates/monty/test_cases/ext_call__elif.py +171 -0
  303. package/ref-monty/crates/monty/test_cases/ext_call__exc.py +4 -0
  304. package/ref-monty/crates/monty/test_cases/ext_call__exc_deep_stack.py +39 -0
  305. package/ref-monty/crates/monty/test_cases/ext_call__exc_in_function.py +17 -0
  306. package/ref-monty/crates/monty/test_cases/ext_call__exc_nested_functions.py +31 -0
  307. package/ref-monty/crates/monty/test_cases/ext_call__ext_exc.py +171 -0
  308. package/ref-monty/crates/monty/test_cases/ext_call__for.py +114 -0
  309. package/ref-monty/crates/monty/test_cases/ext_call__fstring.py +12 -0
  310. package/ref-monty/crates/monty/test_cases/ext_call__if.py +135 -0
  311. package/ref-monty/crates/monty/test_cases/ext_call__if_condition.py +37 -0
  312. package/ref-monty/crates/monty/test_cases/ext_call__in_closure.py +14 -0
  313. package/ref-monty/crates/monty/test_cases/ext_call__in_function.py +40 -0
  314. package/ref-monty/crates/monty/test_cases/ext_call__in_function_simple.py +7 -0
  315. package/ref-monty/crates/monty/test_cases/ext_call__literals.py +17 -0
  316. package/ref-monty/crates/monty/test_cases/ext_call__multi_in_func.py +32 -0
  317. package/ref-monty/crates/monty/test_cases/ext_call__name_lookup.py +69 -0
  318. package/ref-monty/crates/monty/test_cases/ext_call__name_lookup_undefined.py +4 -0
  319. package/ref-monty/crates/monty/test_cases/ext_call__nested_calls.py +14 -0
  320. package/ref-monty/crates/monty/test_cases/ext_call__recursion_bug.py +19 -0
  321. package/ref-monty/crates/monty/test_cases/ext_call__return.py +28 -0
  322. package/ref-monty/crates/monty/test_cases/ext_call__side_effects.py +25 -0
  323. package/ref-monty/crates/monty/test_cases/ext_call__subscript.py +7 -0
  324. package/ref-monty/crates/monty/test_cases/ext_call__ternary.py +28 -0
  325. package/ref-monty/crates/monty/test_cases/ext_call__try.py +280 -0
  326. package/ref-monty/crates/monty/test_cases/ext_call__try_simple.py +10 -0
  327. package/ref-monty/crates/monty/test_cases/ext_call__unary.py +13 -0
  328. package/ref-monty/crates/monty/test_cases/frozenset__ops.py +178 -0
  329. package/ref-monty/crates/monty/test_cases/fstring__all.py +236 -0
  330. package/ref-monty/crates/monty/test_cases/fstring__error_eq_align_on_str.py +3 -0
  331. package/ref-monty/crates/monty/test_cases/fstring__error_float_f_on_str.py +3 -0
  332. package/ref-monty/crates/monty/test_cases/fstring__error_int_d_on_float.py +3 -0
  333. package/ref-monty/crates/monty/test_cases/fstring__error_int_d_on_str.py +3 -0
  334. package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec.py +4 -0
  335. package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec_dynamic.py +4 -0
  336. package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec_str.py +4 -0
  337. package/ref-monty/crates/monty/test_cases/fstring__error_str_s_on_int.py +3 -0
  338. package/ref-monty/crates/monty/test_cases/function__call_duplicate_kwargs.py +6 -0
  339. package/ref-monty/crates/monty/test_cases/function__call_unpack.py +42 -0
  340. package/ref-monty/crates/monty/test_cases/function__defaults.py +117 -0
  341. package/ref-monty/crates/monty/test_cases/function__err_duplicate_arg.py +7 -0
  342. package/ref-monty/crates/monty/test_cases/function__err_duplicate_first_arg.py +7 -0
  343. package/ref-monty/crates/monty/test_cases/function__err_duplicate_kwarg_cleanup.py +9 -0
  344. package/ref-monty/crates/monty/test_cases/function__err_kwonly_as_positional.py +7 -0
  345. package/ref-monty/crates/monty/test_cases/function__err_missing_all_posonly.py +7 -0
  346. package/ref-monty/crates/monty/test_cases/function__err_missing_heap_cleanup.py +9 -0
  347. package/ref-monty/crates/monty/test_cases/function__err_missing_kwonly.py +7 -0
  348. package/ref-monty/crates/monty/test_cases/function__err_missing_posonly_with_kwarg.py +7 -0
  349. package/ref-monty/crates/monty/test_cases/function__err_missing_with_posonly.py +7 -0
  350. package/ref-monty/crates/monty/test_cases/function__err_posonly_as_kwarg.py +7 -0
  351. package/ref-monty/crates/monty/test_cases/function__err_posonly_first_as_kwarg.py +7 -0
  352. package/ref-monty/crates/monty/test_cases/function__err_too_many_posonly.py +7 -0
  353. package/ref-monty/crates/monty/test_cases/function__err_too_many_with_kwonly.py +7 -0
  354. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg.py +7 -0
  355. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_cleanup.py +9 -0
  356. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_quote.py +13 -0
  357. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_simple.py +7 -0
  358. package/ref-monty/crates/monty/test_cases/function__err_unpack_duplicate_arg.py +6 -0
  359. package/ref-monty/crates/monty/test_cases/function__err_unpack_duplicate_heap.py +8 -0
  360. package/ref-monty/crates/monty/test_cases/function__err_unpack_int.py +6 -0
  361. package/ref-monty/crates/monty/test_cases/function__err_unpack_nonstring_key.py +6 -0
  362. package/ref-monty/crates/monty/test_cases/function__err_unpack_not_mapping.py +6 -0
  363. package/ref-monty/crates/monty/test_cases/function__kwargs_unpacking.py +173 -0
  364. package/ref-monty/crates/monty/test_cases/function__ops.py +294 -0
  365. package/ref-monty/crates/monty/test_cases/function__return_none.py +42 -0
  366. package/ref-monty/crates/monty/test_cases/function__signatures.py +47 -0
  367. package/ref-monty/crates/monty/test_cases/function__too_few_args_all.py +6 -0
  368. package/ref-monty/crates/monty/test_cases/function__too_few_args_one.py +6 -0
  369. package/ref-monty/crates/monty/test_cases/function__too_few_args_two.py +6 -0
  370. package/ref-monty/crates/monty/test_cases/function__too_many_args_one.py +6 -0
  371. package/ref-monty/crates/monty/test_cases/function__too_many_args_two.py +6 -0
  372. package/ref-monty/crates/monty/test_cases/function__too_many_args_zero.py +6 -0
  373. package/ref-monty/crates/monty/test_cases/global__error_assigned_before.py +7 -0
  374. package/ref-monty/crates/monty/test_cases/global__ops.py +163 -0
  375. package/ref-monty/crates/monty/test_cases/hash__dict_unhashable.py +2 -0
  376. package/ref-monty/crates/monty/test_cases/hash__list_unhashable.py +2 -0
  377. package/ref-monty/crates/monty/test_cases/hash__ops.py +153 -0
  378. package/ref-monty/crates/monty/test_cases/id__bytes_literals_distinct.py +3 -0
  379. package/ref-monty/crates/monty/test_cases/id__int_copy_distinct.py +5 -0
  380. package/ref-monty/crates/monty/test_cases/id__is_number_is_number.py +3 -0
  381. package/ref-monty/crates/monty/test_cases/id__non_overlapping_lifetimes_distinct_types.py +10 -0
  382. package/ref-monty/crates/monty/test_cases/id__non_overlapping_lifetimes_same_types.py +6 -0
  383. package/ref-monty/crates/monty/test_cases/id__ops.py +97 -0
  384. package/ref-monty/crates/monty/test_cases/id__str_literals_same.py +3 -0
  385. package/ref-monty/crates/monty/test_cases/if__elif_else.py +207 -0
  386. package/ref-monty/crates/monty/test_cases/if__raise_elif.py +11 -0
  387. package/ref-monty/crates/monty/test_cases/if__raise_else.py +13 -0
  388. package/ref-monty/crates/monty/test_cases/if__raise_if.py +9 -0
  389. package/ref-monty/crates/monty/test_cases/if__raise_in_elif_condition.py +18 -0
  390. package/ref-monty/crates/monty/test_cases/if__raise_in_if_condition.py +16 -0
  391. package/ref-monty/crates/monty/test_cases/if_else_expr__all.py +55 -0
  392. package/ref-monty/crates/monty/test_cases/import__error_cannot_import.py +9 -0
  393. package/ref-monty/crates/monty/test_cases/import__error_module_not_found.py +9 -0
  394. package/ref-monty/crates/monty/test_cases/import__local_scope.py +68 -0
  395. package/ref-monty/crates/monty/test_cases/import__os.py +25 -0
  396. package/ref-monty/crates/monty/test_cases/import__relative_error.py +9 -0
  397. package/ref-monty/crates/monty/test_cases/import__relative_no_module_error.py +9 -0
  398. package/ref-monty/crates/monty/test_cases/import__runtime_error_when_executed.py +14 -0
  399. package/ref-monty/crates/monty/test_cases/import__star_error.py +11 -0
  400. package/ref-monty/crates/monty/test_cases/import__sys.py +47 -0
  401. package/ref-monty/crates/monty/test_cases/import__sys_monty.py +28 -0
  402. package/ref-monty/crates/monty/test_cases/import__type_checking_guard.py +37 -0
  403. package/ref-monty/crates/monty/test_cases/import__typing.py +25 -0
  404. package/ref-monty/crates/monty/test_cases/import__typing_type_ignore.py +4 -0
  405. package/ref-monty/crates/monty/test_cases/int__bigint.py +467 -0
  406. package/ref-monty/crates/monty/test_cases/int__bigint_errors.py +260 -0
  407. package/ref-monty/crates/monty/test_cases/int__ops.py +219 -0
  408. package/ref-monty/crates/monty/test_cases/int__overflow_division.py +84 -0
  409. package/ref-monty/crates/monty/test_cases/is_variant__all.py +36 -0
  410. package/ref-monty/crates/monty/test_cases/isinstance__arg2_list_error.py +2 -0
  411. package/ref-monty/crates/monty/test_cases/isinstance__arg2_type_error.py +2 -0
  412. package/ref-monty/crates/monty/test_cases/iter__dict_mutation.py +4 -0
  413. package/ref-monty/crates/monty/test_cases/iter__for.py +243 -0
  414. package/ref-monty/crates/monty/test_cases/iter__for_loop_unpacking.py +66 -0
  415. package/ref-monty/crates/monty/test_cases/iter__generator_expr.py +20 -0
  416. package/ref-monty/crates/monty/test_cases/iter__generator_expr_type.py +7 -0
  417. package/ref-monty/crates/monty/test_cases/iter__not_iterable.py +3 -0
  418. package/ref-monty/crates/monty/test_cases/lambda__all.py +145 -0
  419. package/ref-monty/crates/monty/test_cases/list__extend_not_iterable.py +7 -0
  420. package/ref-monty/crates/monty/test_cases/list__getitem_out_of_bounds.py +3 -0
  421. package/ref-monty/crates/monty/test_cases/list__index_not_found.py +9 -0
  422. package/ref-monty/crates/monty/test_cases/list__index_start_gt_end.py +10 -0
  423. package/ref-monty/crates/monty/test_cases/list__ops.py +473 -0
  424. package/ref-monty/crates/monty/test_cases/list__pop_empty.py +9 -0
  425. package/ref-monty/crates/monty/test_cases/list__pop_out_of_range.py +9 -0
  426. package/ref-monty/crates/monty/test_cases/list__pop_type_error.py +9 -0
  427. package/ref-monty/crates/monty/test_cases/list__remove_not_found.py +9 -0
  428. package/ref-monty/crates/monty/test_cases/list__setitem_dict_index.py +13 -0
  429. package/ref-monty/crates/monty/test_cases/list__setitem_huge_int_index.py +13 -0
  430. package/ref-monty/crates/monty/test_cases/list__setitem_index_error.py +10 -0
  431. package/ref-monty/crates/monty/test_cases/list__setitem_type_error.py +10 -0
  432. package/ref-monty/crates/monty/test_cases/list__unpack_type_error.py +2 -0
  433. package/ref-monty/crates/monty/test_cases/longint__index_error.py +3 -0
  434. package/ref-monty/crates/monty/test_cases/longint__repeat_error.py +3 -0
  435. package/ref-monty/crates/monty/test_cases/loop__break_continue.py +113 -0
  436. package/ref-monty/crates/monty/test_cases/loop__break_finally.py +69 -0
  437. package/ref-monty/crates/monty/test_cases/loop__break_in_function_error.py +13 -0
  438. package/ref-monty/crates/monty/test_cases/loop__break_in_if_error.py +11 -0
  439. package/ref-monty/crates/monty/test_cases/loop__break_nested_except_clears.py +55 -0
  440. package/ref-monty/crates/monty/test_cases/loop__break_outside_error.py +9 -0
  441. package/ref-monty/crates/monty/test_cases/loop__continue_finally.py +81 -0
  442. package/ref-monty/crates/monty/test_cases/loop__continue_in_function_error.py +13 -0
  443. package/ref-monty/crates/monty/test_cases/loop__continue_in_if_error.py +11 -0
  444. package/ref-monty/crates/monty/test_cases/loop__continue_nested_except_clears.py +60 -0
  445. package/ref-monty/crates/monty/test_cases/loop__continue_outside_error.py +9 -0
  446. package/ref-monty/crates/monty/test_cases/math__acos_domain_error.py +11 -0
  447. package/ref-monty/crates/monty/test_cases/math__acosh_domain_error.py +11 -0
  448. package/ref-monty/crates/monty/test_cases/math__asin_domain_error.py +11 -0
  449. package/ref-monty/crates/monty/test_cases/math__atanh_domain_error.py +11 -0
  450. package/ref-monty/crates/monty/test_cases/math__cos_inf_error.py +11 -0
  451. package/ref-monty/crates/monty/test_cases/math__cosh_overflow_error.py +11 -0
  452. package/ref-monty/crates/monty/test_cases/math__exp_overflow_error.py +11 -0
  453. package/ref-monty/crates/monty/test_cases/math__factorial_float_error.py +11 -0
  454. package/ref-monty/crates/monty/test_cases/math__factorial_negative_error.py +11 -0
  455. package/ref-monty/crates/monty/test_cases/math__floor_inf_error.py +11 -0
  456. package/ref-monty/crates/monty/test_cases/math__floor_nan_error.py +11 -0
  457. package/ref-monty/crates/monty/test_cases/math__floor_str_error.py +11 -0
  458. package/ref-monty/crates/monty/test_cases/math__fmod_inf_error.py +11 -0
  459. package/ref-monty/crates/monty/test_cases/math__gamma_neg_int_error.py +11 -0
  460. package/ref-monty/crates/monty/test_cases/math__gcd_float_error.py +11 -0
  461. package/ref-monty/crates/monty/test_cases/math__isqrt_negative_error.py +11 -0
  462. package/ref-monty/crates/monty/test_cases/math__ldexp_overflow_error.py +11 -0
  463. package/ref-monty/crates/monty/test_cases/math__log1p_domain_error.py +11 -0
  464. package/ref-monty/crates/monty/test_cases/math__log_base1_error.py +11 -0
  465. package/ref-monty/crates/monty/test_cases/math__log_zero_error.py +11 -0
  466. package/ref-monty/crates/monty/test_cases/math__module.py +1432 -0
  467. package/ref-monty/crates/monty/test_cases/math__pow_domain_error.py +11 -0
  468. package/ref-monty/crates/monty/test_cases/math__sin_inf_error.py +11 -0
  469. package/ref-monty/crates/monty/test_cases/math__sqrt_negative_error.py +11 -0
  470. package/ref-monty/crates/monty/test_cases/math__tan_inf_error.py +11 -0
  471. package/ref-monty/crates/monty/test_cases/math__trunc_str_error.py +11 -0
  472. package/ref-monty/crates/monty/test_cases/method__args_kwargs_unpacking.py +259 -0
  473. package/ref-monty/crates/monty/test_cases/name_error__unbound_local_func.py +19 -0
  474. package/ref-monty/crates/monty/test_cases/name_error__unbound_local_module.py +12 -0
  475. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_chained.py +9 -0
  476. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_in_expr.py +9 -0
  477. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_in_function.py +16 -0
  478. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_with_args.py +9 -0
  479. package/ref-monty/crates/monty/test_cases/name_error__undefined_global.py +10 -0
  480. package/ref-monty/crates/monty/test_cases/namedtuple__missing_attr.py +11 -0
  481. package/ref-monty/crates/monty/test_cases/namedtuple__ops.py +34 -0
  482. package/ref-monty/crates/monty/test_cases/nonlocal__error_module_level.py +3 -0
  483. package/ref-monty/crates/monty/test_cases/nonlocal__ops.py +353 -0
  484. package/ref-monty/crates/monty/test_cases/os__environ.py +40 -0
  485. package/ref-monty/crates/monty/test_cases/os__getenv_key_list_error.py +5 -0
  486. package/ref-monty/crates/monty/test_cases/os__getenv_key_type_error.py +5 -0
  487. package/ref-monty/crates/monty/test_cases/parse_error__complex.py +3 -0
  488. package/ref-monty/crates/monty/test_cases/pathlib__import.py +11 -0
  489. package/ref-monty/crates/monty/test_cases/pathlib__os.py +136 -0
  490. package/ref-monty/crates/monty/test_cases/pathlib__os_read_error.py +12 -0
  491. package/ref-monty/crates/monty/test_cases/pathlib__pure.py +81 -0
  492. package/ref-monty/crates/monty/test_cases/pyobject__cycle_dict_self.py +5 -0
  493. package/ref-monty/crates/monty/test_cases/pyobject__cycle_list_dict.py +6 -0
  494. package/ref-monty/crates/monty/test_cases/pyobject__cycle_list_self.py +5 -0
  495. package/ref-monty/crates/monty/test_cases/pyobject__cycle_multiple_refs.py +6 -0
  496. package/ref-monty/crates/monty/test_cases/range__error_no_args.py +2 -0
  497. package/ref-monty/crates/monty/test_cases/range__error_step_zero.py +2 -0
  498. package/ref-monty/crates/monty/test_cases/range__error_too_many_args.py +2 -0
  499. package/ref-monty/crates/monty/test_cases/range__getitem_index_error.py +10 -0
  500. package/ref-monty/crates/monty/test_cases/range__ops.py +236 -0
  501. package/ref-monty/crates/monty/test_cases/re__basic.py +756 -0
  502. package/ref-monty/crates/monty/test_cases/re__grouping.py +241 -0
  503. package/ref-monty/crates/monty/test_cases/re__match.py +148 -0
  504. package/ref-monty/crates/monty/test_cases/recursion__deep_drop.py +26 -0
  505. package/ref-monty/crates/monty/test_cases/recursion__deep_eq.py +23 -0
  506. package/ref-monty/crates/monty/test_cases/recursion__deep_hash.py +46 -0
  507. package/ref-monty/crates/monty/test_cases/recursion__deep_repr.py +12 -0
  508. package/ref-monty/crates/monty/test_cases/recursion__function_depth.py +13 -0
  509. package/ref-monty/crates/monty/test_cases/refcount__cycle_mutual_reference.py +18 -0
  510. package/ref-monty/crates/monty/test_cases/refcount__cycle_self_reference.py +12 -0
  511. package/ref-monty/crates/monty/test_cases/refcount__dict_basic.py +5 -0
  512. package/ref-monty/crates/monty/test_cases/refcount__dict_get.py +5 -0
  513. package/ref-monty/crates/monty/test_cases/refcount__dict_keys_and.py +14 -0
  514. package/ref-monty/crates/monty/test_cases/refcount__dict_overwrite.py +6 -0
  515. package/ref-monty/crates/monty/test_cases/refcount__gather_cleanup.py +16 -0
  516. package/ref-monty/crates/monty/test_cases/refcount__gather_exception.py +18 -0
  517. package/ref-monty/crates/monty/test_cases/refcount__gather_nested_cancel.py +25 -0
  518. package/ref-monty/crates/monty/test_cases/refcount__immediate_skipped.py +4 -0
  519. package/ref-monty/crates/monty/test_cases/refcount__kwargs_unpacking.py +27 -0
  520. package/ref-monty/crates/monty/test_cases/refcount__list_append_multiple.py +6 -0
  521. package/ref-monty/crates/monty/test_cases/refcount__list_append_ref.py +5 -0
  522. package/ref-monty/crates/monty/test_cases/refcount__list_concat.py +5 -0
  523. package/ref-monty/crates/monty/test_cases/refcount__list_getitem.py +5 -0
  524. package/ref-monty/crates/monty/test_cases/refcount__list_iadd.py +5 -0
  525. package/ref-monty/crates/monty/test_cases/refcount__nested_list.py +4 -0
  526. package/ref-monty/crates/monty/test_cases/refcount__re_pattern_sub_error_paths.py +37 -0
  527. package/ref-monty/crates/monty/test_cases/refcount__re_search_match.py +34 -0
  528. package/ref-monty/crates/monty/test_cases/refcount__re_sub_error_paths.py +31 -0
  529. package/ref-monty/crates/monty/test_cases/refcount__shared_reference.py +4 -0
  530. package/ref-monty/crates/monty/test_cases/refcount__single_list.py +3 -0
  531. package/ref-monty/crates/monty/test_cases/repr__cycle_detection.py +24 -0
  532. package/ref-monty/crates/monty/test_cases/set__ops.py +191 -0
  533. package/ref-monty/crates/monty/test_cases/set__review_bugs.py +35 -0
  534. package/ref-monty/crates/monty/test_cases/set__unpack_type_error.py +2 -0
  535. package/ref-monty/crates/monty/test_cases/slice__invalid_indices.py +2 -0
  536. package/ref-monty/crates/monty/test_cases/slice__kwargs.py +9 -0
  537. package/ref-monty/crates/monty/test_cases/slice__no_args.py +9 -0
  538. package/ref-monty/crates/monty/test_cases/slice__ops.py +149 -0
  539. package/ref-monty/crates/monty/test_cases/slice__step_zero.py +9 -0
  540. package/ref-monty/crates/monty/test_cases/slice__step_zero_bytes.py +9 -0
  541. package/ref-monty/crates/monty/test_cases/slice__step_zero_range.py +9 -0
  542. package/ref-monty/crates/monty/test_cases/slice__step_zero_str.py +9 -0
  543. package/ref-monty/crates/monty/test_cases/slice__step_zero_tuple.py +9 -0
  544. package/ref-monty/crates/monty/test_cases/slice__too_many_args.py +9 -0
  545. package/ref-monty/crates/monty/test_cases/str__getitem_index_error.py +10 -0
  546. package/ref-monty/crates/monty/test_cases/str__index_not_found.py +9 -0
  547. package/ref-monty/crates/monty/test_cases/str__join_no_args.py +9 -0
  548. package/ref-monty/crates/monty/test_cases/str__join_non_string.py +9 -0
  549. package/ref-monty/crates/monty/test_cases/str__join_not_iterable.py +9 -0
  550. package/ref-monty/crates/monty/test_cases/str__join_too_many_args.py +9 -0
  551. package/ref-monty/crates/monty/test_cases/str__methods.py +327 -0
  552. package/ref-monty/crates/monty/test_cases/str__ops.py +162 -0
  553. package/ref-monty/crates/monty/test_cases/str__partition_empty.py +9 -0
  554. package/ref-monty/crates/monty/test_cases/str__rsplit_empty_sep.py +9 -0
  555. package/ref-monty/crates/monty/test_cases/str__split_empty_sep.py +9 -0
  556. package/ref-monty/crates/monty/test_cases/sys__types.py +7 -0
  557. package/ref-monty/crates/monty/test_cases/traceback__division_error.py +30 -0
  558. package/ref-monty/crates/monty/test_cases/traceback__index_error.py +17 -0
  559. package/ref-monty/crates/monty/test_cases/traceback__insert_as_int.py +10 -0
  560. package/ref-monty/crates/monty/test_cases/traceback__nested_call.py +29 -0
  561. package/ref-monty/crates/monty/test_cases/traceback__nonlocal_module_scope.py +10 -0
  562. package/ref-monty/crates/monty/test_cases/traceback__nonlocal_unbound.py +24 -0
  563. package/ref-monty/crates/monty/test_cases/traceback__range_as_int.py +9 -0
  564. package/ref-monty/crates/monty/test_cases/traceback__recursion_error.py +23 -0
  565. package/ref-monty/crates/monty/test_cases/traceback__set_mutation.py +11 -0
  566. package/ref-monty/crates/monty/test_cases/traceback__undefined_attr_call.py +16 -0
  567. package/ref-monty/crates/monty/test_cases/traceback__undefined_call.py +16 -0
  568. package/ref-monty/crates/monty/test_cases/traceback__undefined_raise.py +16 -0
  569. package/ref-monty/crates/monty/test_cases/try_except__all.py +472 -0
  570. package/ref-monty/crates/monty/test_cases/try_except__bare_raise_no_context.py +2 -0
  571. package/ref-monty/crates/monty/test_cases/try_except__invalid_type.py +5 -0
  572. package/ref-monty/crates/monty/test_cases/tuple__getitem_out_of_bounds.py +3 -0
  573. package/ref-monty/crates/monty/test_cases/tuple__index_not_found.py +9 -0
  574. package/ref-monty/crates/monty/test_cases/tuple__index_start_gt_end.py +10 -0
  575. package/ref-monty/crates/monty/test_cases/tuple__methods.py +19 -0
  576. package/ref-monty/crates/monty/test_cases/tuple__ops.py +133 -0
  577. package/ref-monty/crates/monty/test_cases/tuple__unpack_type_error.py +2 -0
  578. package/ref-monty/crates/monty/test_cases/type__builtin_attr_error.py +9 -0
  579. package/ref-monty/crates/monty/test_cases/type__bytes_negative.py +2 -0
  580. package/ref-monty/crates/monty/test_cases/type__cell_not_builtin.py +9 -0
  581. package/ref-monty/crates/monty/test_cases/type__exception_attr_error.py +11 -0
  582. package/ref-monty/crates/monty/test_cases/type__float_conversion_error.py +2 -0
  583. package/ref-monty/crates/monty/test_cases/type__float_repr_both_quotes.py +9 -0
  584. package/ref-monty/crates/monty/test_cases/type__float_repr_newline.py +9 -0
  585. package/ref-monty/crates/monty/test_cases/type__float_repr_single_quote.py +9 -0
  586. package/ref-monty/crates/monty/test_cases/type__int_conversion_error.py +2 -0
  587. package/ref-monty/crates/monty/test_cases/type__list_not_iterable.py +2 -0
  588. package/ref-monty/crates/monty/test_cases/type__non_builtin_name_error.py +9 -0
  589. package/ref-monty/crates/monty/test_cases/type__ops.py +200 -0
  590. package/ref-monty/crates/monty/test_cases/type__shadow_exc.py +3 -0
  591. package/ref-monty/crates/monty/test_cases/type__shadow_int.py +9 -0
  592. package/ref-monty/crates/monty/test_cases/type__shadow_len.py +3 -0
  593. package/ref-monty/crates/monty/test_cases/type__tuple_not_iterable.py +2 -0
  594. package/ref-monty/crates/monty/test_cases/type_error__int_add_list.py +2 -0
  595. package/ref-monty/crates/monty/test_cases/type_error__int_div_str.py +2 -0
  596. package/ref-monty/crates/monty/test_cases/type_error__int_floordiv_str.py +2 -0
  597. package/ref-monty/crates/monty/test_cases/type_error__int_iadd_str.py +3 -0
  598. package/ref-monty/crates/monty/test_cases/type_error__int_mod_str.py +2 -0
  599. package/ref-monty/crates/monty/test_cases/type_error__int_pow_str.py +2 -0
  600. package/ref-monty/crates/monty/test_cases/type_error__int_sub_str.py +2 -0
  601. package/ref-monty/crates/monty/test_cases/type_error__list_add_int.py +2 -0
  602. package/ref-monty/crates/monty/test_cases/type_error__list_add_str.py +2 -0
  603. package/ref-monty/crates/monty/test_cases/type_error__list_iadd_int.py +6 -0
  604. package/ref-monty/crates/monty/test_cases/type_error__str_add_int.py +2 -0
  605. package/ref-monty/crates/monty/test_cases/type_error__str_iadd_int.py +3 -0
  606. package/ref-monty/crates/monty/test_cases/type_error__unary_invert_str.py +3 -0
  607. package/ref-monty/crates/monty/test_cases/type_error__unary_minus_str.py +4 -0
  608. package/ref-monty/crates/monty/test_cases/type_error__unary_neg_str.py +3 -0
  609. package/ref-monty/crates/monty/test_cases/type_error__unary_plus_str.py +4 -0
  610. package/ref-monty/crates/monty/test_cases/typing__types.py +24 -0
  611. package/ref-monty/crates/monty/test_cases/unpack__nested.py +48 -0
  612. package/ref-monty/crates/monty/test_cases/unpack__non_sequence.py +9 -0
  613. package/ref-monty/crates/monty/test_cases/unpack__not_enough.py +9 -0
  614. package/ref-monty/crates/monty/test_cases/unpack__ops.py +153 -0
  615. package/ref-monty/crates/monty/test_cases/unpack__star_not_enough.py +9 -0
  616. package/ref-monty/crates/monty/test_cases/unpack__too_many.py +9 -0
  617. package/ref-monty/crates/monty/test_cases/version__cpython.py +4 -0
  618. package/ref-monty/crates/monty/test_cases/walrus__all.py +178 -0
  619. package/ref-monty/crates/monty/test_cases/while__all.py +206 -0
  620. package/ref-monty/crates/monty/tests/asyncio.rs +764 -0
  621. package/ref-monty/crates/monty/tests/binary_serde.rs +185 -0
  622. package/ref-monty/crates/monty/tests/bytecode_limits.rs +248 -0
  623. package/ref-monty/crates/monty/tests/datatest_runner.rs +2029 -0
  624. package/ref-monty/crates/monty/tests/inputs.rs +420 -0
  625. package/ref-monty/crates/monty/tests/json_serde.rs +250 -0
  626. package/ref-monty/crates/monty/tests/main.rs +71 -0
  627. package/ref-monty/crates/monty/tests/math_module.rs +114 -0
  628. package/ref-monty/crates/monty/tests/name_lookup.rs +482 -0
  629. package/ref-monty/crates/monty/tests/os_tests.rs +459 -0
  630. package/ref-monty/crates/monty/tests/parse_errors.rs +441 -0
  631. package/ref-monty/crates/monty/tests/print_writer.rs +238 -0
  632. package/ref-monty/crates/monty/tests/py_object.rs +121 -0
  633. package/ref-monty/crates/monty/tests/regex.rs +90 -0
  634. package/ref-monty/crates/monty/tests/repl.rs +344 -0
  635. package/ref-monty/crates/monty/tests/resource_limits.rs +1826 -0
  636. package/ref-monty/crates/monty/tests/try_from.rs +167 -0
  637. package/ref-monty/crates/monty-cli/Cargo.toml +25 -0
  638. package/ref-monty/crates/monty-cli/src/main.rs +541 -0
  639. package/ref-monty/crates/monty-js/.cargo/config.toml +2 -0
  640. package/ref-monty/crates/monty-js/.prettierignore +8 -0
  641. package/ref-monty/crates/monty-js/Cargo.toml +32 -0
  642. package/ref-monty/crates/monty-js/README.md +207 -0
  643. package/ref-monty/crates/monty-js/__test__/async.spec.ts +350 -0
  644. package/ref-monty/crates/monty-js/__test__/basic.spec.ts +114 -0
  645. package/ref-monty/crates/monty-js/__test__/exceptions.spec.ts +427 -0
  646. package/ref-monty/crates/monty-js/__test__/external.spec.ts +354 -0
  647. package/ref-monty/crates/monty-js/__test__/inputs.spec.ts +143 -0
  648. package/ref-monty/crates/monty-js/__test__/limits.spec.ts +162 -0
  649. package/ref-monty/crates/monty-js/__test__/package.json +3 -0
  650. package/ref-monty/crates/monty-js/__test__/print.spec.ts +229 -0
  651. package/ref-monty/crates/monty-js/__test__/repl.spec.ts +34 -0
  652. package/ref-monty/crates/monty-js/__test__/serialize.spec.ts +205 -0
  653. package/ref-monty/crates/monty-js/__test__/start.spec.ts +443 -0
  654. package/ref-monty/crates/monty-js/__test__/type_check.spec.ts +147 -0
  655. package/ref-monty/crates/monty-js/__test__/types.spec.ts +319 -0
  656. package/ref-monty/crates/monty-js/build.rs +61 -0
  657. package/ref-monty/crates/monty-js/index-header.d.ts +3 -0
  658. package/ref-monty/crates/monty-js/package-lock.json +4694 -0
  659. package/ref-monty/crates/monty-js/package.json +100 -0
  660. package/ref-monty/crates/monty-js/scripts/smoke-test.sh +69 -0
  661. package/ref-monty/crates/monty-js/smoke-test/package.json +17 -0
  662. package/ref-monty/crates/monty-js/smoke-test/test.ts +171 -0
  663. package/ref-monty/crates/monty-js/smoke-test/tsconfig.json +11 -0
  664. package/ref-monty/crates/monty-js/src/convert.rs +648 -0
  665. package/ref-monty/crates/monty-js/src/exceptions.rs +293 -0
  666. package/ref-monty/crates/monty-js/src/lib.rs +41 -0
  667. package/ref-monty/crates/monty-js/src/limits.rs +53 -0
  668. package/ref-monty/crates/monty-js/src/monty_cls.rs +1407 -0
  669. package/ref-monty/crates/monty-js/tsconfig.json +17 -0
  670. package/ref-monty/crates/monty-js/wrapper.ts +701 -0
  671. package/ref-monty/crates/monty-python/Cargo.toml +38 -0
  672. package/ref-monty/crates/monty-python/README.md +134 -0
  673. package/ref-monty/crates/monty-python/build.rs +4 -0
  674. package/ref-monty/crates/monty-python/example.py +40 -0
  675. package/ref-monty/crates/monty-python/exercise.py +46 -0
  676. package/ref-monty/crates/monty-python/pyproject.toml +57 -0
  677. package/ref-monty/crates/monty-python/python/pydantic_monty/__init__.py +281 -0
  678. package/ref-monty/crates/monty-python/python/pydantic_monty/_monty.pyi +677 -0
  679. package/ref-monty/crates/monty-python/python/pydantic_monty/os_access.py +933 -0
  680. package/ref-monty/crates/monty-python/python/pydantic_monty/py.typed +0 -0
  681. package/ref-monty/crates/monty-python/src/convert.rs +273 -0
  682. package/ref-monty/crates/monty-python/src/dataclass.rs +461 -0
  683. package/ref-monty/crates/monty-python/src/exceptions.rs +557 -0
  684. package/ref-monty/crates/monty-python/src/external.rs +165 -0
  685. package/ref-monty/crates/monty-python/src/lib.rs +77 -0
  686. package/ref-monty/crates/monty-python/src/limits.rs +142 -0
  687. package/ref-monty/crates/monty-python/src/monty_cls.rs +1650 -0
  688. package/ref-monty/crates/monty-python/src/repl.rs +470 -0
  689. package/ref-monty/crates/monty-python/src/serialization.rs +761 -0
  690. package/ref-monty/crates/monty-python/tests/test_async.py +1201 -0
  691. package/ref-monty/crates/monty-python/tests/test_basic.py +66 -0
  692. package/ref-monty/crates/monty-python/tests/test_dataclasses.py +971 -0
  693. package/ref-monty/crates/monty-python/tests/test_exceptions.py +361 -0
  694. package/ref-monty/crates/monty-python/tests/test_external.py +367 -0
  695. package/ref-monty/crates/monty-python/tests/test_inputs.py +126 -0
  696. package/ref-monty/crates/monty-python/tests/test_limits.py +257 -0
  697. package/ref-monty/crates/monty-python/tests/test_os_access.py +1286 -0
  698. package/ref-monty/crates/monty-python/tests/test_os_access_compat.py +731 -0
  699. package/ref-monty/crates/monty-python/tests/test_os_access_raw.py +483 -0
  700. package/ref-monty/crates/monty-python/tests/test_os_calls.py +819 -0
  701. package/ref-monty/crates/monty-python/tests/test_print.py +208 -0
  702. package/ref-monty/crates/monty-python/tests/test_re.py +170 -0
  703. package/ref-monty/crates/monty-python/tests/test_readme_examples.py +20 -0
  704. package/ref-monty/crates/monty-python/tests/test_repl.py +749 -0
  705. package/ref-monty/crates/monty-python/tests/test_serialize.py +284 -0
  706. package/ref-monty/crates/monty-python/tests/test_start.py +346 -0
  707. package/ref-monty/crates/monty-python/tests/test_threading.py +163 -0
  708. package/ref-monty/crates/monty-python/tests/test_type_check.py +344 -0
  709. package/ref-monty/crates/monty-python/tests/test_types.py +553 -0
  710. package/ref-monty/crates/monty-type-checking/Cargo.toml +32 -0
  711. package/ref-monty/crates/monty-type-checking/src/db.rs +116 -0
  712. package/ref-monty/crates/monty-type-checking/src/lib.rs +4 -0
  713. package/ref-monty/crates/monty-type-checking/src/type_check.rs +280 -0
  714. package/ref-monty/crates/monty-type-checking/tests/bad_types.py +109 -0
  715. package/ref-monty/crates/monty-type-checking/tests/bad_types_output.txt +21 -0
  716. package/ref-monty/crates/monty-type-checking/tests/good_types.py +475 -0
  717. package/ref-monty/crates/monty-type-checking/tests/main.rs +205 -0
  718. package/ref-monty/crates/monty-type-checking/tests/reveal_types.py +56 -0
  719. package/ref-monty/crates/monty-type-checking/tests/reveal_types_output.txt +41 -0
  720. package/ref-monty/crates/monty-typeshed/Cargo.toml +29 -0
  721. package/ref-monty/crates/monty-typeshed/README.md +11 -0
  722. package/ref-monty/crates/monty-typeshed/build.rs +101 -0
  723. package/ref-monty/crates/monty-typeshed/custom/README.md +1 -0
  724. package/ref-monty/crates/monty-typeshed/custom/asyncio.pyi +138 -0
  725. package/ref-monty/crates/monty-typeshed/custom/os.pyi +87 -0
  726. package/ref-monty/crates/monty-typeshed/custom/sys.pyi +33 -0
  727. package/ref-monty/crates/monty-typeshed/src/lib.rs +56 -0
  728. package/ref-monty/crates/monty-typeshed/update.py +321 -0
  729. package/ref-monty/crates/monty-typeshed/vendor/typeshed/source_commit.txt +1 -0
  730. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/VERSIONS +20 -0
  731. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/_collections_abc.pyi +105 -0
  732. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/_typeshed/__init__.pyi +394 -0
  733. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/asyncio.pyi +138 -0
  734. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/builtins.pyi +1434 -0
  735. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/collections/__init__.pyi +527 -0
  736. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/collections/abc.pyi +2 -0
  737. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/dataclasses.pyi +502 -0
  738. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/enum.pyi +376 -0
  739. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/math.pyi +149 -0
  740. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/os.pyi +87 -0
  741. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/pathlib/__init__.pyi +395 -0
  742. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/pathlib/types.pyi +8 -0
  743. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/re.pyi +337 -0
  744. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/sys.pyi +33 -0
  745. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/types.pyi +741 -0
  746. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/typing.pyi +1217 -0
  747. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/typing_extensions.pyi +716 -0
  748. package/ref-monty/docs/usage-guide.md +117 -0
  749. package/ref-monty/examples/README.md +3 -0
  750. package/ref-monty/examples/expense_analysis/README.md +3 -0
  751. package/ref-monty/examples/expense_analysis/data.py +124 -0
  752. package/ref-monty/examples/expense_analysis/main.py +115 -0
  753. package/ref-monty/examples/sql_playground/README.md +20 -0
  754. package/ref-monty/examples/sql_playground/external_functions.py +129 -0
  755. package/ref-monty/examples/sql_playground/main.py +81 -0
  756. package/ref-monty/examples/sql_playground/sandbox_code.py +82 -0
  757. package/ref-monty/examples/sql_playground/type_stubs.pyi +14 -0
  758. package/ref-monty/examples/web_scraper/README.md +15 -0
  759. package/ref-monty/examples/web_scraper/browser.py +56 -0
  760. package/ref-monty/examples/web_scraper/example_code.py +59 -0
  761. package/ref-monty/examples/web_scraper/external_functions.py +324 -0
  762. package/ref-monty/examples/web_scraper/main.py +193 -0
  763. package/ref-monty/examples/web_scraper/sub_agent.py +79 -0
  764. package/ref-monty/monty-npm.md +235 -0
  765. package/ref-monty/pyproject.toml +162 -0
  766. package/ref-monty/scripts/check_imports.py +91 -0
  767. package/ref-monty/scripts/codecov_diff.py +412 -0
  768. package/ref-monty/scripts/complete_tests.py +146 -0
  769. package/ref-monty/scripts/flamegraph_to_text.py +208 -0
  770. package/ref-monty/scripts/iter_test_methods.py +540 -0
  771. package/ref-monty/scripts/run_traceback.py +180 -0
  772. package/ref-monty/scripts/startup_performance.py +130 -0
  773. package/ref-monty/uv.lock +1779 -0
  774. package/temp_resend_cli/repo/.github/scripts/pr-title-check.js +34 -0
  775. package/temp_resend_cli/repo/.github/workflows/ci.yml +67 -0
  776. package/temp_resend_cli/repo/.github/workflows/post-release.yml +51 -0
  777. package/temp_resend_cli/repo/.github/workflows/pr-title-check.yml +13 -0
  778. package/temp_resend_cli/repo/.github/workflows/release.yml +175 -0
  779. package/temp_resend_cli/repo/.github/workflows/test-install-unix.yml +34 -0
  780. package/temp_resend_cli/repo/.github/workflows/test-install-windows.yml +48 -0
  781. package/temp_resend_cli/repo/CHANGELOG.md +31 -0
  782. package/temp_resend_cli/repo/LICENSE +21 -0
  783. package/temp_resend_cli/repo/README.md +450 -0
  784. package/temp_resend_cli/repo/biome.json +36 -0
  785. package/temp_resend_cli/repo/install.ps1 +141 -0
  786. package/temp_resend_cli/repo/install.sh +301 -0
  787. package/temp_resend_cli/repo/package.json +61 -0
  788. package/temp_resend_cli/repo/pnpm-lock.yaml +2439 -0
  789. package/temp_resend_cli/repo/renovate.json +4 -0
  790. package/temp_resend_cli/repo/src/cli.ts +98 -0
  791. package/temp_resend_cli/repo/src/commands/api-keys/create.ts +114 -0
  792. package/temp_resend_cli/repo/src/commands/api-keys/delete.ts +47 -0
  793. package/temp_resend_cli/repo/src/commands/api-keys/index.ts +26 -0
  794. package/temp_resend_cli/repo/src/commands/api-keys/list.ts +35 -0
  795. package/temp_resend_cli/repo/src/commands/api-keys/utils.ts +8 -0
  796. package/temp_resend_cli/repo/src/commands/auth/index.ts +20 -0
  797. package/temp_resend_cli/repo/src/commands/auth/login.ts +234 -0
  798. package/temp_resend_cli/repo/src/commands/auth/logout.ts +105 -0
  799. package/temp_resend_cli/repo/src/commands/broadcasts/create.ts +196 -0
  800. package/temp_resend_cli/repo/src/commands/broadcasts/delete.ts +46 -0
  801. package/temp_resend_cli/repo/src/commands/broadcasts/get.ts +59 -0
  802. package/temp_resend_cli/repo/src/commands/broadcasts/index.ts +43 -0
  803. package/temp_resend_cli/repo/src/commands/broadcasts/list.ts +60 -0
  804. package/temp_resend_cli/repo/src/commands/broadcasts/send.ts +56 -0
  805. package/temp_resend_cli/repo/src/commands/broadcasts/update.ts +95 -0
  806. package/temp_resend_cli/repo/src/commands/broadcasts/utils.ts +35 -0
  807. package/temp_resend_cli/repo/src/commands/contact-properties/create.ts +118 -0
  808. package/temp_resend_cli/repo/src/commands/contact-properties/delete.ts +48 -0
  809. package/temp_resend_cli/repo/src/commands/contact-properties/get.ts +46 -0
  810. package/temp_resend_cli/repo/src/commands/contact-properties/index.ts +48 -0
  811. package/temp_resend_cli/repo/src/commands/contact-properties/list.ts +68 -0
  812. package/temp_resend_cli/repo/src/commands/contact-properties/update.ts +88 -0
  813. package/temp_resend_cli/repo/src/commands/contact-properties/utils.ts +17 -0
  814. package/temp_resend_cli/repo/src/commands/contacts/add-segment.ts +78 -0
  815. package/temp_resend_cli/repo/src/commands/contacts/create.ts +122 -0
  816. package/temp_resend_cli/repo/src/commands/contacts/delete.ts +49 -0
  817. package/temp_resend_cli/repo/src/commands/contacts/get.ts +53 -0
  818. package/temp_resend_cli/repo/src/commands/contacts/index.ts +58 -0
  819. package/temp_resend_cli/repo/src/commands/contacts/list.ts +57 -0
  820. package/temp_resend_cli/repo/src/commands/contacts/remove-segment.ts +48 -0
  821. package/temp_resend_cli/repo/src/commands/contacts/segments.ts +39 -0
  822. package/temp_resend_cli/repo/src/commands/contacts/topics.ts +45 -0
  823. package/temp_resend_cli/repo/src/commands/contacts/update-topics.ts +90 -0
  824. package/temp_resend_cli/repo/src/commands/contacts/update.ts +77 -0
  825. package/temp_resend_cli/repo/src/commands/contacts/utils.ts +119 -0
  826. package/temp_resend_cli/repo/src/commands/doctor.ts +216 -0
  827. package/temp_resend_cli/repo/src/commands/domains/create.ts +83 -0
  828. package/temp_resend_cli/repo/src/commands/domains/delete.ts +42 -0
  829. package/temp_resend_cli/repo/src/commands/domains/get.ts +47 -0
  830. package/temp_resend_cli/repo/src/commands/domains/index.ts +35 -0
  831. package/temp_resend_cli/repo/src/commands/domains/list.ts +53 -0
  832. package/temp_resend_cli/repo/src/commands/domains/update.ts +75 -0
  833. package/temp_resend_cli/repo/src/commands/domains/utils.ts +44 -0
  834. package/temp_resend_cli/repo/src/commands/domains/verify.ts +38 -0
  835. package/temp_resend_cli/repo/src/commands/emails/batch.ts +140 -0
  836. package/temp_resend_cli/repo/src/commands/emails/get.ts +44 -0
  837. package/temp_resend_cli/repo/src/commands/emails/index.ts +30 -0
  838. package/temp_resend_cli/repo/src/commands/emails/list.ts +84 -0
  839. package/temp_resend_cli/repo/src/commands/emails/receiving/attachment.ts +55 -0
  840. package/temp_resend_cli/repo/src/commands/emails/receiving/attachments.ts +68 -0
  841. package/temp_resend_cli/repo/src/commands/emails/receiving/get.ts +58 -0
  842. package/temp_resend_cli/repo/src/commands/emails/receiving/index.ts +28 -0
  843. package/temp_resend_cli/repo/src/commands/emails/receiving/list.ts +59 -0
  844. package/temp_resend_cli/repo/src/commands/emails/receiving/utils.ts +38 -0
  845. package/temp_resend_cli/repo/src/commands/emails/send.ts +189 -0
  846. package/temp_resend_cli/repo/src/commands/open.ts +27 -0
  847. package/temp_resend_cli/repo/src/commands/segments/create.ts +50 -0
  848. package/temp_resend_cli/repo/src/commands/segments/delete.ts +47 -0
  849. package/temp_resend_cli/repo/src/commands/segments/get.ts +38 -0
  850. package/temp_resend_cli/repo/src/commands/segments/index.ts +36 -0
  851. package/temp_resend_cli/repo/src/commands/segments/list.ts +58 -0
  852. package/temp_resend_cli/repo/src/commands/segments/utils.ts +7 -0
  853. package/temp_resend_cli/repo/src/commands/teams/index.ts +10 -0
  854. package/temp_resend_cli/repo/src/commands/teams/list.ts +35 -0
  855. package/temp_resend_cli/repo/src/commands/teams/remove.ts +86 -0
  856. package/temp_resend_cli/repo/src/commands/teams/switch.ts +76 -0
  857. package/temp_resend_cli/repo/src/commands/topics/create.ts +73 -0
  858. package/temp_resend_cli/repo/src/commands/topics/delete.ts +47 -0
  859. package/temp_resend_cli/repo/src/commands/topics/get.ts +42 -0
  860. package/temp_resend_cli/repo/src/commands/topics/index.ts +42 -0
  861. package/temp_resend_cli/repo/src/commands/topics/list.ts +34 -0
  862. package/temp_resend_cli/repo/src/commands/topics/update.ts +59 -0
  863. package/temp_resend_cli/repo/src/commands/topics/utils.ts +16 -0
  864. package/temp_resend_cli/repo/src/commands/webhooks/create.ts +128 -0
  865. package/temp_resend_cli/repo/src/commands/webhooks/delete.ts +49 -0
  866. package/temp_resend_cli/repo/src/commands/webhooks/get.ts +42 -0
  867. package/temp_resend_cli/repo/src/commands/webhooks/index.ts +42 -0
  868. package/temp_resend_cli/repo/src/commands/webhooks/list.ts +55 -0
  869. package/temp_resend_cli/repo/src/commands/webhooks/listen.ts +379 -0
  870. package/temp_resend_cli/repo/src/commands/webhooks/update.ts +83 -0
  871. package/temp_resend_cli/repo/src/commands/webhooks/utils.ts +36 -0
  872. package/temp_resend_cli/repo/src/commands/whoami.ts +71 -0
  873. package/temp_resend_cli/repo/src/lib/actions.ts +157 -0
  874. package/temp_resend_cli/repo/src/lib/client.ts +37 -0
  875. package/temp_resend_cli/repo/src/lib/config.ts +217 -0
  876. package/temp_resend_cli/repo/src/lib/files.ts +15 -0
  877. package/temp_resend_cli/repo/src/lib/help-text.ts +38 -0
  878. package/temp_resend_cli/repo/src/lib/output.ts +56 -0
  879. package/temp_resend_cli/repo/src/lib/pagination.ts +36 -0
  880. package/temp_resend_cli/repo/src/lib/prompts.ts +149 -0
  881. package/temp_resend_cli/repo/src/lib/spinner.ts +100 -0
  882. package/temp_resend_cli/repo/src/lib/table.ts +57 -0
  883. package/temp_resend_cli/repo/src/lib/tty.ts +28 -0
  884. package/temp_resend_cli/repo/src/lib/update-check.ts +169 -0
  885. package/temp_resend_cli/repo/src/lib/version.ts +4 -0
  886. package/temp_resend_cli/repo/tests/commands/api-keys/create.test.ts +196 -0
  887. package/temp_resend_cli/repo/tests/commands/api-keys/delete.test.ts +157 -0
  888. package/temp_resend_cli/repo/tests/commands/api-keys/list.test.ts +134 -0
  889. package/temp_resend_cli/repo/tests/commands/auth/login.test.ts +153 -0
  890. package/temp_resend_cli/repo/tests/commands/auth/logout.test.ts +153 -0
  891. package/temp_resend_cli/repo/tests/commands/broadcasts/create.test.ts +454 -0
  892. package/temp_resend_cli/repo/tests/commands/broadcasts/delete.test.ts +183 -0
  893. package/temp_resend_cli/repo/tests/commands/broadcasts/get.test.ts +147 -0
  894. package/temp_resend_cli/repo/tests/commands/broadcasts/list.test.ts +199 -0
  895. package/temp_resend_cli/repo/tests/commands/broadcasts/send.test.ts +162 -0
  896. package/temp_resend_cli/repo/tests/commands/broadcasts/update.test.ts +288 -0
  897. package/temp_resend_cli/repo/tests/commands/contact-properties/create.test.ts +251 -0
  898. package/temp_resend_cli/repo/tests/commands/contact-properties/delete.test.ts +184 -0
  899. package/temp_resend_cli/repo/tests/commands/contact-properties/get.test.ts +145 -0
  900. package/temp_resend_cli/repo/tests/commands/contact-properties/list.test.ts +181 -0
  901. package/temp_resend_cli/repo/tests/commands/contact-properties/update.test.ts +217 -0
  902. package/temp_resend_cli/repo/tests/commands/contacts/add-segment.test.ts +189 -0
  903. package/temp_resend_cli/repo/tests/commands/contacts/create.test.ts +271 -0
  904. package/temp_resend_cli/repo/tests/commands/contacts/delete.test.ts +193 -0
  905. package/temp_resend_cli/repo/tests/commands/contacts/get.test.ts +149 -0
  906. package/temp_resend_cli/repo/tests/commands/contacts/list.test.ts +176 -0
  907. package/temp_resend_cli/repo/tests/commands/contacts/remove-segment.test.ts +167 -0
  908. package/temp_resend_cli/repo/tests/commands/contacts/segments.test.ts +168 -0
  909. package/temp_resend_cli/repo/tests/commands/contacts/topics.test.ts +164 -0
  910. package/temp_resend_cli/repo/tests/commands/contacts/update-topics.test.ts +248 -0
  911. package/temp_resend_cli/repo/tests/commands/contacts/update.test.ts +206 -0
  912. package/temp_resend_cli/repo/tests/commands/doctor.test.ts +164 -0
  913. package/temp_resend_cli/repo/tests/commands/domains/create.test.ts +193 -0
  914. package/temp_resend_cli/repo/tests/commands/domains/delete.test.ts +157 -0
  915. package/temp_resend_cli/repo/tests/commands/domains/get.test.ts +138 -0
  916. package/temp_resend_cli/repo/tests/commands/domains/list.test.ts +165 -0
  917. package/temp_resend_cli/repo/tests/commands/domains/update.test.ts +224 -0
  918. package/temp_resend_cli/repo/tests/commands/domains/verify.test.ts +118 -0
  919. package/temp_resend_cli/repo/tests/commands/emails/batch.test.ts +324 -0
  920. package/temp_resend_cli/repo/tests/commands/emails/get.test.ts +132 -0
  921. package/temp_resend_cli/repo/tests/commands/emails/receiving/attachment.test.ts +141 -0
  922. package/temp_resend_cli/repo/tests/commands/emails/receiving/attachments.test.ts +169 -0
  923. package/temp_resend_cli/repo/tests/commands/emails/receiving/get.test.ts +141 -0
  924. package/temp_resend_cli/repo/tests/commands/emails/receiving/list.test.ts +182 -0
  925. package/temp_resend_cli/repo/tests/commands/emails/send.test.ts +312 -0
  926. package/temp_resend_cli/repo/tests/commands/segments/create.test.ts +164 -0
  927. package/temp_resend_cli/repo/tests/commands/segments/delete.test.ts +183 -0
  928. package/temp_resend_cli/repo/tests/commands/segments/get.test.ts +138 -0
  929. package/temp_resend_cli/repo/tests/commands/segments/list.test.ts +174 -0
  930. package/temp_resend_cli/repo/tests/commands/teams/list.test.ts +62 -0
  931. package/temp_resend_cli/repo/tests/commands/teams/remove.test.ts +110 -0
  932. package/temp_resend_cli/repo/tests/commands/teams/switch.test.ts +103 -0
  933. package/temp_resend_cli/repo/tests/commands/topics/create.test.ts +192 -0
  934. package/temp_resend_cli/repo/tests/commands/topics/delete.test.ts +157 -0
  935. package/temp_resend_cli/repo/tests/commands/topics/get.test.ts +126 -0
  936. package/temp_resend_cli/repo/tests/commands/topics/list.test.ts +125 -0
  937. package/temp_resend_cli/repo/tests/commands/topics/update.test.ts +178 -0
  938. package/temp_resend_cli/repo/tests/commands/webhooks/create.test.ts +225 -0
  939. package/temp_resend_cli/repo/tests/commands/webhooks/delete.test.ts +157 -0
  940. package/temp_resend_cli/repo/tests/commands/webhooks/get.test.ts +126 -0
  941. package/temp_resend_cli/repo/tests/commands/webhooks/list.test.ts +178 -0
  942. package/temp_resend_cli/repo/tests/commands/webhooks/update.test.ts +207 -0
  943. package/temp_resend_cli/repo/tests/commands/whoami.test.ts +98 -0
  944. package/temp_resend_cli/repo/tests/e2e/smoke.test.ts +93 -0
  945. package/temp_resend_cli/repo/tests/helpers.ts +86 -0
  946. package/temp_resend_cli/repo/tests/lib/client.test.ts +71 -0
  947. package/temp_resend_cli/repo/tests/lib/config.test.ts +451 -0
  948. package/temp_resend_cli/repo/tests/lib/files.test.ts +73 -0
  949. package/temp_resend_cli/repo/tests/lib/help-text.test.ts +97 -0
  950. package/temp_resend_cli/repo/tests/lib/output.test.ts +136 -0
  951. package/temp_resend_cli/repo/tests/lib/prompts.test.ts +185 -0
  952. package/temp_resend_cli/repo/tests/lib/spinner.test.ts +166 -0
  953. package/temp_resend_cli/repo/tests/lib/table.test.ts +63 -0
  954. package/temp_resend_cli/repo/tests/lib/tty.test.ts +89 -0
  955. package/temp_resend_cli/repo/tests/lib/update-check.test.ts +179 -0
  956. package/temp_resend_cli/repo/tsconfig.json +14 -0
  957. package/temp_resend_cli/repo/vitest.config.e2e.ts +8 -0
  958. package/temp_resend_cli/repo/vitest.config.ts +10 -0
  959. package/tests/test-mcp-browser-use-smoke.sh +28 -56
  960. package/tests/test-monty-smoke.sh +32 -0
  961. package/tests/test-resend-smoke.sh +36 -0
@@ -0,0 +1,1826 @@
1
+ /// Tests for resource limits and garbage collection.
2
+ ///
3
+ /// These tests verify that the `ResourceTracker` system correctly enforces
4
+ /// allocation limits, time limits, and triggers garbage collection.
5
+ use std::time::{Duration, Instant};
6
+
7
+ use monty::{
8
+ ExcType, LimitedTracker, MontyObject, MontyRun, NameLookupResult, PrintWriter, ResourceLimits, RunProgress,
9
+ };
10
+
11
+ /// Resolves consecutive `NameLookup` yields by providing a `Function` object for each name.
12
+ ///
13
+ /// External functions are no longer declared upfront. Instead, the VM yields `NameLookup`
14
+ /// when it encounters an unresolved name. This helper resolves all such lookups until
15
+ /// a different progress variant is reached.
16
+ fn resolve_name_lookups<T: monty::ResourceTracker>(
17
+ mut progress: RunProgress<T>,
18
+ ) -> Result<RunProgress<T>, monty::MontyException> {
19
+ while let RunProgress::NameLookup(lookup) = progress {
20
+ let name = lookup.name.clone();
21
+ progress = lookup.resume(
22
+ NameLookupResult::Value(MontyObject::Function { name, docstring: None }),
23
+ PrintWriter::Stdout,
24
+ )?;
25
+ }
26
+ Ok(progress)
27
+ }
28
+
29
+ /// Test that GC properly collects dict cycles via the has_refs() check in allocate().
30
+ ///
31
+ /// This test creates cycles using dict literals and dict setitem. Dict setitem
32
+ /// does NOT call mark_potential_cycle(), so the ONLY way may_have_cycles gets
33
+ /// set is through the has_refs() check when allocating a dict with refs.
34
+ ///
35
+ /// If has_refs() is disabled, this test will FAIL because GC never runs.
36
+ #[test]
37
+ #[cfg(feature = "ref-count-return")]
38
+ fn gc_collects_dict_cycles_via_has_refs() {
39
+ // Create 200,001 dict cycles. Each iteration:
40
+ // - Creates empty dict d1
41
+ // - Creates dict d2 = {'ref': d1} - d2 is allocated WITH a ref to d1
42
+ // This triggers has_refs() which sets may_have_cycles = true
43
+ // - Sets d1['ref'] = d2 - creates cycle d1 <-> d2
44
+ // Dict setitem does NOT call mark_potential_cycle()
45
+ // - On next iteration, both dicts are reassigned, making the cycle unreachable
46
+ //
47
+ // GC runs every 100,000 allocations. With 200,001 iterations:
48
+ // - GC runs at 100k (collects cycles 0-49,999 approximately)
49
+ // - GC runs at 200k (collects more cycles)
50
+ // After GC runs, only the final cycle should remain.
51
+ let code = r"
52
+ # Create many dict cycles
53
+ for i in range(200001):
54
+ d1 = {}
55
+ d2 = {'ref': d1} # d2 allocated WITH ref - has_refs() must trigger here
56
+ d1['ref'] = d2 # Cycle formed - dict setitem does NOT call mark_potential_cycle
57
+
58
+ # Create final result (not a cycle)
59
+ result = 'done'
60
+ result
61
+ ";
62
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
63
+
64
+ let output = ex.run_ref_counts(vec![]).expect("should succeed");
65
+
66
+ // GC_INTERVAL is 100,000. With 200,001 iterations creating dict cycles,
67
+ // GC must have run at least once, resetting allocations_since_gc.
68
+ // If may_have_cycles was never set (has_refs() disabled), GC never runs
69
+ // and allocations_since_gc would be ~400k (2 dicts per iteration).
70
+ assert!(
71
+ output.allocations_since_gc < 100_000,
72
+ "GC should have run (has_refs() must set may_have_cycles): allocations_since_gc = {}",
73
+ output.allocations_since_gc
74
+ );
75
+
76
+ // Verify that GC collected most cycles.
77
+ // If GC failed to collect cycles, heap_count would be >> 400k.
78
+ // We allow a small number of extra objects for implementation details.
79
+ assert!(
80
+ output.heap_count < 20,
81
+ "GC should collect most unreachable dict cycles: {} heap objects (expected < 20)",
82
+ output.heap_count
83
+ );
84
+ }
85
+
86
+ /// Test that GC properly collects self-referencing list cycles.
87
+ ///
88
+ /// This test creates cycles using list.append(), which calls mark_potential_cycle().
89
+ /// This tests the mutation-based cycle detection path.
90
+ #[test]
91
+ #[cfg(feature = "ref-count-return")]
92
+ fn gc_collects_list_cycles() {
93
+ // Create 200,001 self-referencing list cycles. Each iteration:
94
+ // - Creates empty list `a`
95
+ // - Appends `a` to itself (creating a self-reference cycle)
96
+ // This calls mark_potential_cycle() and sets may_have_cycles = true
97
+ // - On next iteration, `a` is reassigned, making the cycle unreachable
98
+ //
99
+ // GC runs every 100,000 allocations. With 200,001 iterations:
100
+ // - GC runs at 100k (collects cycles 0-99,999)
101
+ // - GC runs at 200k (collects cycles 100k-199,999)
102
+ // After GC runs, only the final cycle should remain.
103
+ let code = r"
104
+ # Create many self-referencing list cycles
105
+ for i in range(200001):
106
+ a = []
107
+ a.append(a) # Creates cycle via list.append() which calls mark_potential_cycle()
108
+
109
+ # Create final result (not a cycle)
110
+ result = [1, 2, 3]
111
+ len(result)
112
+ ";
113
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
114
+
115
+ let output = ex.run_ref_counts(vec![]).expect("should succeed");
116
+
117
+ // GC_INTERVAL is 100,000. With 200,001 iterations creating list cycles,
118
+ // GC must have run at least twice, resetting allocations_since_gc.
119
+ assert!(
120
+ output.allocations_since_gc < 100_000,
121
+ "GC should have run: allocations_since_gc = {}",
122
+ output.allocations_since_gc
123
+ );
124
+
125
+ // Verify that GC collected most cycles.
126
+ // If GC failed to collect cycles, heap_count would be >> 200k.
127
+ assert!(
128
+ output.heap_count < 20,
129
+ "GC should collect most unreachable list cycles: {} heap objects (expected < 20)",
130
+ output.heap_count
131
+ );
132
+
133
+ // Verify expected ref counts
134
+ // `a` is the last self-referencing list (refcount 2: variable + self-reference)
135
+ // `result` is a simple list (refcount 1: just the variable)
136
+ assert_eq!(
137
+ output.counts.get("a"),
138
+ Some(&2),
139
+ "self-referencing list should have refcount 2"
140
+ );
141
+ assert_eq!(
142
+ output.counts.get("result"),
143
+ Some(&1),
144
+ "result list should have refcount 1"
145
+ );
146
+ }
147
+
148
+ /// Test that allocation limits return an error.
149
+ #[test]
150
+ fn allocation_limit_exceeded() {
151
+ // Use multi-character strings to ensure heap allocation (single ASCII chars are interned)
152
+ let code = r"
153
+ result = []
154
+ for i in range(100, 115):
155
+ result.append(str(i))
156
+ result
157
+ ";
158
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
159
+
160
+ let limits = ResourceLimits::new().max_allocations(4);
161
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
162
+
163
+ // Should fail due to allocation limit
164
+ assert!(result.is_err(), "should exceed allocation limit");
165
+ let exc = result.unwrap_err();
166
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
167
+ assert!(
168
+ exc.message().is_some_and(|m| m.contains("allocation limit exceeded")),
169
+ "expected allocation limit error, got: {exc}"
170
+ );
171
+ }
172
+
173
+ #[test]
174
+ fn allocation_limit_not_exceeded() {
175
+ // Single-digit strings are interned (no allocation), so this uses minimal heap
176
+ let code = r"
177
+ result = []
178
+ for i in range(9):
179
+ result.append(str(i))
180
+ result
181
+ ";
182
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
183
+
184
+ // Allocations: list (1) + range (1) + iterator (1) = 3
185
+ // Note: str(0)...str(8) are single ASCII chars, so they use pre-interned strings
186
+ let limits = ResourceLimits::new().max_allocations(5);
187
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
188
+
189
+ // Should succeed
190
+ assert!(result.is_ok(), "should not exceed allocation limit");
191
+ }
192
+
193
+ #[test]
194
+ fn time_limit_exceeded() {
195
+ // Create a long-running loop using for + range (while isn't implemented yet)
196
+ // Use a very large range to ensure it runs long enough to hit the time limit
197
+ let code = r"
198
+ x = 0
199
+ for i in range(100000000):
200
+ x = x + 1
201
+ x
202
+ ";
203
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
204
+
205
+ // Set a short time limit
206
+ let limits = ResourceLimits::new().max_duration(Duration::from_millis(50));
207
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
208
+
209
+ // Should fail due to time limit
210
+ assert!(result.is_err(), "should exceed time limit");
211
+ let exc = result.unwrap_err();
212
+ assert_eq!(exc.exc_type(), ExcType::TimeoutError);
213
+ assert!(
214
+ exc.message().is_some_and(|m| m.contains("time limit exceeded")),
215
+ "expected time limit error, got: {exc}"
216
+ );
217
+ }
218
+
219
+ #[test]
220
+ fn time_limit_not_exceeded() {
221
+ // Simple code that runs quickly
222
+ let code = "x = 1 + 2\nx";
223
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
224
+
225
+ // Set a generous time limit
226
+ let limits = ResourceLimits::new().max_duration(Duration::from_secs(5));
227
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
228
+
229
+ // Should succeed
230
+ assert!(result.is_ok(), "should not exceed time limit");
231
+ }
232
+
233
+ /// Test that memory limits return an error.
234
+ #[test]
235
+ fn memory_limit_exceeded() {
236
+ // Create code that builds up memory using lists
237
+ // Each iteration creates a new list that gets appended
238
+ let code = r"
239
+ result = []
240
+ for i in range(100):
241
+ result.append([1, 2, 3, 4, 5])
242
+ result
243
+ ";
244
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
245
+
246
+ // Set a very low memory limit (100 bytes) to trigger on nested list allocation
247
+ let limits = ResourceLimits::new().max_memory(100);
248
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
249
+
250
+ // Should fail due to memory limit
251
+ assert!(result.is_err(), "should exceed memory limit");
252
+ let exc = result.unwrap_err();
253
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
254
+ assert!(
255
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
256
+ "expected memory limit error, got: {exc}"
257
+ );
258
+ }
259
+
260
+ #[test]
261
+ fn combined_limits() {
262
+ // Test multiple limits together
263
+ let code = "x = 1 + 2\nx";
264
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
265
+
266
+ let limits = ResourceLimits::new()
267
+ .max_allocations(1000)
268
+ .max_duration(Duration::from_secs(5))
269
+ .max_memory(1024 * 1024);
270
+
271
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
272
+ assert!(result.is_ok(), "should succeed with generous limits");
273
+ }
274
+
275
+ #[test]
276
+ fn run_without_limits_succeeds() {
277
+ // Verify that run() still works (no limits)
278
+ let code = r"
279
+ result = []
280
+ for i in range(100):
281
+ result.append(str(i))
282
+ len(result)
283
+ ";
284
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
285
+
286
+ // Standard run should succeed
287
+ let result = ex.run_no_limits(vec![]);
288
+ assert!(result.is_ok(), "standard run should succeed");
289
+ }
290
+
291
+ #[test]
292
+ fn gc_interval_triggers_collection() {
293
+ // This test verifies that GC can run without crashing
294
+ // We can't easily verify that GC actually collected anything without
295
+ // adding more introspection, but we can verify it runs
296
+ let code = r"
297
+ result = []
298
+ for i in range(100):
299
+ temp = [1, 2, 3]
300
+ result.append(i)
301
+ len(result)
302
+ ";
303
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
304
+
305
+ // Set GC to run every 10 allocations
306
+ let limits = ResourceLimits::new().gc_interval(10);
307
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
308
+
309
+ assert!(result.is_ok(), "should succeed with GC enabled");
310
+ }
311
+
312
+ #[test]
313
+ #[cfg_attr(
314
+ feature = "ref-count-panic",
315
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
316
+ )]
317
+ fn executor_iter_resource_limit_on_resume() {
318
+ // Test that resource limits are enforced across function calls
319
+ // First function call succeeds, but resumed execution exceeds limit
320
+
321
+ // f-string to create multi-char strings (not interned)
322
+ let code = "foo(1)\nx = []\nfor i in range(10):\n x.append(f'x{i}')\nlen(x)";
323
+ let run = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
324
+
325
+ // First function call should succeed with generous limit
326
+ let limits = ResourceLimits::new().max_allocations(5);
327
+ let progress = run
328
+ .start(vec![], LimitedTracker::new(limits), PrintWriter::Stdout)
329
+ .unwrap();
330
+ let call = resolve_name_lookups(progress)
331
+ .unwrap()
332
+ .into_function_call()
333
+ .expect("function call");
334
+ assert_eq!(call.function_name, "foo");
335
+ assert_eq!(call.args, vec![MontyObject::Int(1)]);
336
+
337
+ // Resume - should fail due to allocation limit during the for loop
338
+ let result = call.resume(MontyObject::None, PrintWriter::Stdout);
339
+ assert!(result.is_err(), "should exceed allocation limit on resume");
340
+ let exc = result.unwrap_err();
341
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
342
+ assert!(
343
+ exc.message().is_some_and(|m| m.contains("allocation limit exceeded")),
344
+ "expected allocation limit error, got: {exc}"
345
+ );
346
+ }
347
+
348
+ #[test]
349
+ #[cfg_attr(
350
+ feature = "ref-count-panic",
351
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
352
+ )]
353
+ fn executor_iter_resource_limit_before_function_call() {
354
+ // Test that resource limits are enforced before first function call
355
+
356
+ // f-string to create multi-char strings (not interned)
357
+ let code = "x = []\nfor i in range(10):\n x.append(f'x{i}')\nfoo(len(x))\n42";
358
+ let run = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
359
+
360
+ // Should fail before reaching the function call
361
+ let limits = ResourceLimits::new().max_allocations(3);
362
+ let result = run.start(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
363
+
364
+ assert!(result.is_err(), "should exceed allocation limit before function call");
365
+ let exc = result.unwrap_err();
366
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
367
+ assert!(
368
+ exc.message().is_some_and(|m| m.contains("allocation limit exceeded")),
369
+ "expected allocation limit error, got: {exc}"
370
+ );
371
+ }
372
+
373
+ #[test]
374
+ #[cfg_attr(
375
+ feature = "ref-count-panic",
376
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
377
+ )]
378
+ fn char_f_string_not_allocated() {
379
+ // Single character f-string interned not not allocated
380
+
381
+ let code = "x = []\nfor i in range(10):\n x.append(f'{i}')";
382
+ let run = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
383
+
384
+ let limits = ResourceLimits::new().max_allocations(4);
385
+ run.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout)
386
+ .unwrap();
387
+ }
388
+
389
+ #[test]
390
+ fn executor_iter_resource_limit_multiple_function_calls() {
391
+ // Test resource limits across multiple function calls
392
+ let code = "foo(1)\nbar(2)\nbaz(3)\n4";
393
+ let run = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
394
+
395
+ // Very tight allocation limit - should still work for simple function calls
396
+ let limits = ResourceLimits::new().max_allocations(100);
397
+
398
+ let progress = run
399
+ .start(vec![], LimitedTracker::new(limits), PrintWriter::Stdout)
400
+ .unwrap();
401
+ let call = resolve_name_lookups(progress)
402
+ .unwrap()
403
+ .into_function_call()
404
+ .expect("first call");
405
+ assert_eq!(call.function_name, "foo");
406
+ assert_eq!(call.args, vec![MontyObject::Int(1)]);
407
+
408
+ let progress = call.resume(MontyObject::None, PrintWriter::Stdout).unwrap();
409
+ let call = resolve_name_lookups(progress)
410
+ .unwrap()
411
+ .into_function_call()
412
+ .expect("second call");
413
+ assert_eq!(call.function_name, "bar");
414
+ assert_eq!(call.args, vec![MontyObject::Int(2)]);
415
+
416
+ let progress = call.resume(MontyObject::None, PrintWriter::Stdout).unwrap();
417
+ let call = resolve_name_lookups(progress)
418
+ .unwrap()
419
+ .into_function_call()
420
+ .expect("third call");
421
+ assert_eq!(call.function_name, "baz");
422
+ assert_eq!(call.args, vec![MontyObject::Int(3)]);
423
+
424
+ let result = call
425
+ .resume(MontyObject::None, PrintWriter::Stdout)
426
+ .unwrap()
427
+ .into_complete()
428
+ .expect("complete");
429
+ assert_eq!(result, MontyObject::Int(4));
430
+ }
431
+
432
+ /// Test that deep recursion triggers memory limit due to namespace tracking.
433
+ ///
434
+ /// Function call namespaces (local variables) are tracked by ResourceTracker.
435
+ /// Each recursive call creates a new namespace, which should count against
436
+ /// the memory limit.
437
+ #[test]
438
+ #[cfg_attr(
439
+ feature = "ref-count-panic",
440
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
441
+ )]
442
+ fn recursion_respects_memory_limit() {
443
+ // Recursive function that creates stack frames with local variables
444
+ let code = r"
445
+ def recurse(n):
446
+ x = 1
447
+ if n > 0:
448
+ return recurse(n - 1)
449
+ return 0
450
+ recurse(1000)
451
+ ";
452
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
453
+
454
+ // Very tight memory limit - should fail due to namespace memory
455
+ // Each frame needs at least namespace_size * size_of::<Value>() bytes
456
+ let limits = ResourceLimits::new().max_memory(1000);
457
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
458
+
459
+ assert!(result.is_err(), "should exceed memory limit from recursion");
460
+ let exc = result.unwrap_err();
461
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
462
+ assert!(
463
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
464
+ "expected memory limit error, got: {exc}"
465
+ );
466
+ }
467
+
468
+ /// Test that recursion depth limit returns an error.
469
+ #[test]
470
+ #[cfg_attr(
471
+ feature = "ref-count-panic",
472
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
473
+ )]
474
+ fn recursion_depth_limit_exceeded() {
475
+ let code = r"
476
+ def recurse(n):
477
+ if n > 0:
478
+ return recurse(n - 1)
479
+ return 0
480
+ recurse(100)
481
+ ";
482
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
483
+
484
+ // Set recursion limit to 10
485
+ let limits = ResourceLimits::new().max_recursion_depth(Some(10));
486
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
487
+
488
+ assert!(result.is_err(), "should exceed recursion depth limit");
489
+ let exc = result.unwrap_err();
490
+ assert_eq!(exc.exc_type(), ExcType::RecursionError);
491
+ assert!(
492
+ exc.message()
493
+ .is_some_and(|m| m.contains("maximum recursion depth exceeded")),
494
+ "expected recursion depth error, got: {exc}"
495
+ );
496
+ }
497
+
498
+ #[test]
499
+ fn recursion_depth_limit_not_exceeded() {
500
+ let code = r"
501
+ def recurse(n):
502
+ if n > 0:
503
+ return recurse(n - 1)
504
+ return 0
505
+ recurse(5)
506
+ ";
507
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
508
+
509
+ // Set recursion limit to 10 - should succeed with 5 levels
510
+ let limits = ResourceLimits::new().max_recursion_depth(Some(10));
511
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
512
+
513
+ assert!(result.is_ok(), "should not exceed recursion depth limit");
514
+ }
515
+
516
+ // === BigInt large result pre-check tests ===
517
+ // These tests verify that operations that would produce very large BigInt results
518
+ // are rejected before the computation begins, preventing DoS attacks.
519
+
520
+ /// Test that large pow operations are rejected by memory limits.
521
+ #[test]
522
+ fn bigint_pow_memory_limit() {
523
+ // 2 ** 10_000_000 would produce ~1.25MB result
524
+ let code = "2 ** 10000000";
525
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
526
+
527
+ // Set a 1MB memory limit - should fail before computing
528
+ let limits = ResourceLimits::new().max_memory(1_000_000);
529
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
530
+
531
+ assert!(result.is_err(), "large pow should exceed memory limit");
532
+ let exc = result.unwrap_err();
533
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
534
+ assert!(
535
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
536
+ "expected memory limit error, got: {exc}"
537
+ );
538
+ }
539
+
540
+ /// Test that pow with huge exponents is rejected even when the size estimate overflows u64.
541
+ ///
542
+ /// This catches a bug where `estimate_pow_bytes` returned `None` on u64 overflow,
543
+ /// and the `if let Some(estimated)` pattern silently skipped the check.
544
+ #[test]
545
+ fn pow_overflowing_estimate_rejected() {
546
+ // base ~63 bits, exp ~62 bits: estimated result bits = 63 * 3962939411543162624 overflows u64
547
+ let code = "-7234189268083315611 ** 3962939411543162624";
548
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
549
+
550
+ let limits = ResourceLimits::new().max_memory(1_000_000);
551
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
552
+
553
+ assert!(result.is_err(), "pow with overflowing estimate should be rejected");
554
+ let exc = result.unwrap_err();
555
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
556
+ assert!(
557
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
558
+ "expected memory limit error, got: {exc}"
559
+ );
560
+ }
561
+
562
+ /// Test that pow with a large base and moderate exponent is rejected by memory limits.
563
+ ///
564
+ /// `-7234408281351689115 ** 65327` has a 63-bit base, so the result is ~63*65327 ≈ 4M bits ≈ 514KB.
565
+ /// With a 100KB memory limit the pre-check should reject this before computing.
566
+ #[test]
567
+ fn pow_large_base_moderate_exp_rejected() {
568
+ let code = "-7234408281351689115 ** 65327";
569
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
570
+
571
+ let limits = ResourceLimits::new().max_memory(100_000);
572
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
573
+
574
+ assert!(result.is_err(), "large pow should exceed memory limit");
575
+ let exc = result.unwrap_err();
576
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
577
+ assert!(
578
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
579
+ "expected memory limit error, got: {exc}"
580
+ );
581
+ }
582
+
583
+ /// Test that the 4× safety multiplier for pow intermediate allocations catches
584
+ /// cases where the final result fits but repeated-squaring intermediates don't.
585
+ ///
586
+ /// `2 ** 500000`: final result = 2 * 500000 bits = 125KB. Without multiplier this
587
+ /// passes a 200KB limit. With 4× multiplier: 500KB > 200KB → rejected.
588
+ #[test]
589
+ fn pow_intermediate_allocation_multiplier() {
590
+ let code = "2 ** 500000";
591
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
592
+
593
+ // 200KB limit: final result (125KB) fits, but 4× estimate (500KB) exceeds it
594
+ let limits = ResourceLimits::new().max_memory(200_000);
595
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
596
+
597
+ assert!(
598
+ result.is_err(),
599
+ "pow should be rejected due to intermediate allocation overhead"
600
+ );
601
+ let exc = result.unwrap_err();
602
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
603
+ // 2 bits * 500000 = 125KB final, × 4 = 500072 bytes (includes base memory offset)
604
+ assert_eq!(
605
+ exc.message(),
606
+ Some("memory limit exceeded: 500072 bytes > 200000 bytes")
607
+ );
608
+ }
609
+
610
+ /// Test that pow still succeeds when the 4× estimate is within the limit.
611
+ ///
612
+ /// `2 ** 100000`: final result = 2 * 100000 bits ≈ 25KB. With 4× multiplier: ~100KB.
613
+ /// A 1MB limit should comfortably allow this.
614
+ #[test]
615
+ fn pow_within_limit_with_multiplier() {
616
+ let code = "x = 2 ** 100000\nx > 0";
617
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
618
+
619
+ let limits = ResourceLimits::new().max_memory(1_000_000);
620
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
621
+
622
+ assert!(result.is_ok(), "pow with 4× estimate under limit should succeed");
623
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
624
+ }
625
+
626
+ /// Test the exact fuzzer OOM pattern: right-associative chained exponentiation.
627
+ ///
628
+ /// `3 ** 3661666` is the first sub-expression of the fuzzer input
629
+ /// `1666**3**366**3**3661666`. Since `**` is right-associative, `3**3661666`
630
+ /// is computed first. Base 3 has 2 bits, so: 2 * 3661666 = 7323332 bits ≈ 915KB.
631
+ /// With 4× multiplier: 3660KB > 1MB fuzz limit → rejected.
632
+ #[test]
633
+ fn pow_fuzzer_oom_chained_exponentiation() {
634
+ // This is the subexpression that caused the fuzzer OOM
635
+ let code = "3 ** 3661666";
636
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
637
+
638
+ // 1MB limit (matching the fuzzer's resource limit)
639
+ let limits = ResourceLimits::new().max_memory(1_024 * 1_024);
640
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
641
+
642
+ assert!(
643
+ result.is_err(),
644
+ "fuzzer OOM pattern should be rejected by 4× multiplier"
645
+ );
646
+ let exc = result.unwrap_err();
647
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
648
+ // 2 bits * 3661666 = 915KB final, × 4 = 3661740 bytes
649
+ assert_eq!(
650
+ exc.message(),
651
+ Some("memory limit exceeded: 3661740 bytes > 1048576 bytes")
652
+ );
653
+ }
654
+
655
+ /// Test the full fuzzer input that originally caused OOM.
656
+ ///
657
+ /// The input `1666**3**366**3**3661666` should be rejected before any large
658
+ /// intermediate allocation occurs.
659
+ #[test]
660
+ fn pow_fuzzer_oom_full_input() {
661
+ let code = "1666**3**366**3**3661666";
662
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
663
+
664
+ let limits = ResourceLimits::new().max_memory(1_024 * 1_024);
665
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
666
+
667
+ assert!(result.is_err(), "full fuzzer OOM input should be rejected");
668
+ let exc = result.unwrap_err();
669
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
670
+ // 3**3661666 is evaluated first (right-associative). Base 3 = 2 bits,
671
+ // so estimate = 2 * 3661666 bits = 915KB. With 4× multiplier: 3661740 bytes > 1MB.
672
+ assert_eq!(
673
+ exc.message(),
674
+ Some("memory limit exceeded: 3661740 bytes > 1048576 bytes")
675
+ );
676
+ }
677
+
678
+ /// Test that large left shift operations are rejected by memory limits.
679
+ #[test]
680
+ fn bigint_lshift_memory_limit() {
681
+ // 1 << 10_000_000 would produce ~1.25MB result
682
+ let code = "1 << 10000000";
683
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
684
+
685
+ // Set a 1MB memory limit - should fail before computing
686
+ let limits = ResourceLimits::new().max_memory(1_000_000);
687
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
688
+
689
+ assert!(result.is_err(), "large lshift should exceed memory limit");
690
+ let exc = result.unwrap_err();
691
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
692
+ assert!(
693
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
694
+ "expected memory limit error, got: {exc}"
695
+ );
696
+ }
697
+
698
+ /// Test that large multiplication operations are rejected by memory limits.
699
+ #[test]
700
+ fn bigint_mult_memory_limit() {
701
+ // (2**4_000_000) * (2**4_000_000) would produce ~1MB result
702
+ let code = "big = 2 ** 4000000\nbig * big";
703
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
704
+
705
+ // Set a 1MB memory limit - should fail before computing the multiplication
706
+ let limits = ResourceLimits::new().max_memory(1_000_000);
707
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
708
+
709
+ assert!(result.is_err(), "large mult should exceed memory limit");
710
+ let exc = result.unwrap_err();
711
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
712
+ assert!(
713
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
714
+ "expected memory limit error, got: {exc}"
715
+ );
716
+ }
717
+
718
+ /// Test that small BigInt operations succeed within memory limits.
719
+ #[test]
720
+ fn bigint_small_operations_within_limit() {
721
+ // 2 ** 1000 produces ~125 bytes - well under limit
722
+ let code = "x = 2 ** 1000\ny = 1 << 1000\nz = x * 2\nx > 0 and y > 0 and z > 0";
723
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
724
+
725
+ // Set a 1MB memory limit - should succeed
726
+ let limits = ResourceLimits::new().max_memory(1_000_000);
727
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
728
+
729
+ assert!(result.is_ok(), "small BigInt operations should succeed within limit");
730
+ let val = result.unwrap();
731
+ assert_eq!(val, MontyObject::Bool(true));
732
+ }
733
+
734
+ /// Test that edge cases (0, 1, -1) with huge exponents succeed even with limits.
735
+ /// These produce constant-size results regardless of exponent.
736
+ #[test]
737
+ fn bigint_edge_cases_always_succeed() {
738
+ // Test each edge case individually to minimize other allocations
739
+ // These edge cases produce constant-size results regardless of exponent:
740
+ // - 0 ** huge = 0
741
+ // - 1 ** huge = 1
742
+ // - (-1) ** huge = 1 or -1
743
+ // - 0 << huge = 0
744
+
745
+ // 1MB limit would reject 2**10000000 (~1.25MB) but allows edge cases
746
+ let limits = ResourceLimits::new().max_memory(1_000_000);
747
+
748
+ // 0 ** huge = 0
749
+ let code = "0 ** 10000000";
750
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
751
+ let result = ex.run(vec![], LimitedTracker::new(limits.clone()), PrintWriter::Stdout);
752
+ assert!(result.is_ok(), "0 ** huge should succeed");
753
+ assert_eq!(result.unwrap(), MontyObject::Int(0));
754
+
755
+ // 1 ** huge = 1
756
+ let code = "1 ** 10000000";
757
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
758
+ let result = ex.run(vec![], LimitedTracker::new(limits.clone()), PrintWriter::Stdout);
759
+ assert!(result.is_ok(), "1 ** huge should succeed");
760
+ assert_eq!(result.unwrap(), MontyObject::Int(1));
761
+
762
+ // (-1) ** huge_even = 1
763
+ let code = "(-1) ** 10000000";
764
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
765
+ let result = ex.run(vec![], LimitedTracker::new(limits.clone()), PrintWriter::Stdout);
766
+ assert!(result.is_ok(), "(-1) ** huge_even should succeed");
767
+ assert_eq!(result.unwrap(), MontyObject::Int(1));
768
+
769
+ // (-1) ** huge_odd = -1
770
+ let code = "(-1) ** 10000001";
771
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
772
+ let result = ex.run(vec![], LimitedTracker::new(limits.clone()), PrintWriter::Stdout);
773
+ assert!(result.is_ok(), "(-1) ** huge_odd should succeed");
774
+ assert_eq!(result.unwrap(), MontyObject::Int(-1));
775
+
776
+ // 0 << huge = 0
777
+ let code = "0 << 10000000";
778
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
779
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
780
+ assert!(result.is_ok(), "0 << huge should succeed");
781
+ assert_eq!(result.unwrap(), MontyObject::Int(0));
782
+ }
783
+
784
+ /// Test that pow() builtin also respects memory limits.
785
+ #[test]
786
+ fn bigint_builtin_pow_memory_limit() {
787
+ let code = "pow(2, 10000000)";
788
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
789
+
790
+ let limits = ResourceLimits::new().max_memory(1_000_000);
791
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
792
+
793
+ assert!(result.is_err(), "builtin pow should respect memory limit");
794
+ let exc = result.unwrap_err();
795
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
796
+ }
797
+
798
+ /// Test that large BigInt operations are rejected BEFORE allocation via check_large_result.
799
+ ///
800
+ /// The pre-allocation size check estimates result size and rejects operations that would
801
+ /// exceed the memory limit before any memory is actually consumed.
802
+ #[test]
803
+ fn bigint_rejected_before_allocation() {
804
+ // 2**1000000: base 2 has 2 bits, so estimate = 2 * 1000000 bits = 250KB
805
+ // With 4× safety multiplier for intermediate allocations = 1000KB
806
+ // Set limit to 100KB - the pre-check should reject before allocating
807
+ let code = "2 ** 1000000";
808
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
809
+
810
+ let limits = ResourceLimits::new().max_memory(100_000); // 100KB limit
811
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
812
+
813
+ assert!(result.is_err(), "should be rejected before allocation");
814
+ let exc = result.unwrap_err();
815
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
816
+ assert_eq!(
817
+ exc.message(),
818
+ Some("memory limit exceeded: 1000072 bytes > 100000 bytes")
819
+ );
820
+ }
821
+
822
+ // === String/Bytes large result pre-check tests ===
823
+ // These tests verify that string/bytes multiplication operations that would produce
824
+ // very large results are rejected before the computation begins.
825
+
826
+ /// Test that large string multiplication is rejected before allocation.
827
+ #[test]
828
+ fn string_mult_memory_limit() {
829
+ // 'x' * 1000000 = 1MB string
830
+ let code = "'x' * 1000000";
831
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
832
+
833
+ let limits = ResourceLimits::new().max_memory(100_000); // 100KB limit
834
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
835
+
836
+ assert!(result.is_err(), "large string mult should be rejected");
837
+ let exc = result.unwrap_err();
838
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
839
+ assert!(
840
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
841
+ "expected memory limit error, got: {exc}"
842
+ );
843
+ }
844
+
845
+ /// Test that large bytes multiplication is rejected before allocation.
846
+ #[test]
847
+ fn bytes_mult_memory_limit() {
848
+ // b'x' * 1000000 = 1MB bytes
849
+ let code = "b'x' * 1000000";
850
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
851
+
852
+ let limits = ResourceLimits::new().max_memory(100_000); // 100KB limit
853
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
854
+
855
+ assert!(result.is_err(), "large bytes mult should be rejected");
856
+ let exc = result.unwrap_err();
857
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
858
+ assert!(
859
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
860
+ "expected memory limit error, got: {exc}"
861
+ );
862
+ }
863
+
864
+ /// Test that small string multiplication works within limits.
865
+ #[test]
866
+ fn string_mult_within_limit() {
867
+ // 'abc' * 100 = 300 bytes, well within 100KB limit
868
+ let code = "'abc' * 100 == 'abc' * 100";
869
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
870
+
871
+ let limits = ResourceLimits::new().max_memory(100_000);
872
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
873
+
874
+ assert!(result.is_ok(), "small string mult should succeed");
875
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
876
+ }
877
+
878
+ /// Test that small bytes multiplication works within limits.
879
+ #[test]
880
+ fn bytes_mult_within_limit() {
881
+ // b'abc' * 100 = 300 bytes, well within 100KB limit
882
+ let code = "b'abc' * 100 == b'abc' * 100";
883
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
884
+
885
+ let limits = ResourceLimits::new().max_memory(100_000);
886
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
887
+
888
+ assert!(result.is_ok(), "small bytes mult should succeed");
889
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
890
+ }
891
+
892
+ /// Test that string multiplication is rejected before allocation via check_large_result.
893
+ #[test]
894
+ fn string_mult_rejected_before_allocation() {
895
+ // 'x' * 200000 = 200KB string
896
+ // Set limit to 100KB - the pre-check should reject before allocating
897
+ let code = "'x' * 200000";
898
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
899
+
900
+ let limits = ResourceLimits::new().max_memory(100_000); // 100KB limit
901
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
902
+
903
+ assert!(result.is_err(), "should be rejected before allocation");
904
+ let exc = result.unwrap_err();
905
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
906
+ // The exact size may include some overhead, but should be around 200KB
907
+ assert!(
908
+ exc.message()
909
+ .is_some_and(|m| m.contains("memory limit exceeded") && m.contains("> 100000 bytes")),
910
+ "expected memory limit error with ~200KB size, got: {:?}",
911
+ exc.message()
912
+ );
913
+ }
914
+
915
+ /// Test that large list multiplication is rejected before allocation.
916
+ #[test]
917
+ fn list_mult_memory_limit() {
918
+ // [1] * 10000 = 10,000 Values = ~160KB (at 16 bytes per Value)
919
+ let code = "[1] * 10000";
920
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
921
+
922
+ let limits = ResourceLimits::new().max_memory(100_000); // 100KB limit
923
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
924
+
925
+ assert!(result.is_err(), "large list mult should be rejected");
926
+ let exc = result.unwrap_err();
927
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
928
+ assert!(
929
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
930
+ "expected memory limit error, got: {exc}"
931
+ );
932
+ }
933
+
934
+ /// Test that large tuple multiplication is rejected before allocation.
935
+ #[test]
936
+ fn tuple_mult_memory_limit() {
937
+ // (1,) * 10000 = 10,000 Values = ~160KB (at 16 bytes per Value)
938
+ let code = "(1,) * 10000";
939
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
940
+
941
+ let limits = ResourceLimits::new().max_memory(100_000); // 100KB limit
942
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
943
+
944
+ assert!(result.is_err(), "large tuple mult should be rejected");
945
+ let exc = result.unwrap_err();
946
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
947
+ assert!(
948
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
949
+ "expected memory limit error, got: {exc}"
950
+ );
951
+ }
952
+
953
+ /// Test that small list multiplication works within limits.
954
+ #[test]
955
+ fn list_mult_within_limit() {
956
+ // [1, 2, 3] * 20 = 60 Values, well within 100KB limit
957
+ let code = "[1, 2, 3] * 20 == [1, 2, 3] * 20";
958
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
959
+
960
+ let limits = ResourceLimits::new().max_memory(100_000);
961
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
962
+
963
+ assert!(result.is_ok(), "small list mult should succeed");
964
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
965
+ }
966
+
967
+ /// Test that `int * bytes` (int on left) is also rejected by the pre-check.
968
+ ///
969
+ /// This catches a bug where interned bytes/strings bypassed the `mult_sequence`
970
+ /// pre-check because `py_mult` handled `InternBytes * Int` inline without
971
+ /// checking resource limits.
972
+ #[test]
973
+ fn int_times_bytes_memory_limit() {
974
+ // int on left side: 1000000 * b'x' = 1MB
975
+ let code = "1000000 * b'x'";
976
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
977
+
978
+ let limits = ResourceLimits::new().max_memory(100_000); // 100KB limit
979
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
980
+
981
+ assert!(result.is_err(), "int * bytes should be rejected");
982
+ let exc = result.unwrap_err();
983
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
984
+ assert!(
985
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
986
+ "expected memory limit error, got: {exc}"
987
+ );
988
+ }
989
+
990
+ /// Test that `int * str` (int on left) is also rejected by the pre-check.
991
+ #[test]
992
+ fn int_times_string_memory_limit() {
993
+ // int on left side: 1000000 * 'x' = 1MB
994
+ let code = "1000000 * 'x'";
995
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
996
+
997
+ let limits = ResourceLimits::new().max_memory(100_000); // 100KB limit
998
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
999
+
1000
+ assert!(result.is_err(), "int * str should be rejected");
1001
+ let exc = result.unwrap_err();
1002
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1003
+ assert!(
1004
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
1005
+ "expected memory limit error, got: {exc}"
1006
+ );
1007
+ }
1008
+
1009
+ /// Test that `bigint * bytes` (LongInt on left) is rejected by the pre-check.
1010
+ #[test]
1011
+ fn longint_times_bytes_memory_limit() {
1012
+ // i64::MAX + 1 = 9223372036854775808, which is a LongInt but fits in usize on 64-bit.
1013
+ // Multiplied by 1-byte bytes literal, this would be ~9.2 exabytes.
1014
+ let code = "9223372036854775808 * b'x'";
1015
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1016
+
1017
+ let limits = ResourceLimits::new().max_memory(100_000);
1018
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1019
+
1020
+ assert!(result.is_err(), "bigint * bytes should be rejected");
1021
+ let exc = result.unwrap_err();
1022
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1023
+ assert!(
1024
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
1025
+ "expected memory limit error, got: {exc}"
1026
+ );
1027
+ }
1028
+
1029
+ /// Test that `bigint * str` (LongInt on left) is rejected by the pre-check.
1030
+ #[test]
1031
+ fn longint_times_string_memory_limit() {
1032
+ // i64::MAX + 1 = 9223372036854775808, which is a LongInt but fits in usize on 64-bit.
1033
+ let code = "9223372036854775808 * 'x'";
1034
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1035
+
1036
+ let limits = ResourceLimits::new().max_memory(100_000);
1037
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1038
+
1039
+ assert!(result.is_err(), "bigint * str should be rejected");
1040
+ let exc = result.unwrap_err();
1041
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1042
+ assert!(
1043
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
1044
+ "expected memory limit error, got: {exc}"
1045
+ );
1046
+ }
1047
+
1048
+ /// Test that small tuple multiplication works within limits.
1049
+ #[test]
1050
+ fn tuple_mult_within_limit() {
1051
+ // (1, 2, 3) * 20 = 60 Values, well within 100KB limit
1052
+ let code = "(1, 2, 3) * 20 == (1, 2, 3) * 20";
1053
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1054
+
1055
+ let limits = ResourceLimits::new().max_memory(100_000);
1056
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1057
+
1058
+ assert!(result.is_ok(), "small tuple mult should succeed");
1059
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
1060
+ }
1061
+
1062
+ // === Timeout enforcement in builtin iteration loops ===
1063
+ // These tests verify that `max_duration_secs` is enforced inside Rust-side loops
1064
+ // within builtin functions. Previously, builtins like sum(), sorted(), min(), max()
1065
+ // ran Rust loops entirely within a single bytecode instruction, bypassing the VM's
1066
+ // per-instruction timeout check. The fix adds `heap.check_time()` calls inside
1067
+ // `MontyIter::for_next()` and other non-iterator loops.
1068
+
1069
+ /// Helper: runs code with a short time limit and asserts it produces a TimeoutError promptly.
1070
+ fn assert_timeout_in_builtin(code: &str, label: &str) {
1071
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1072
+
1073
+ let limits = ResourceLimits::new().max_duration(Duration::from_millis(100));
1074
+ let start = std::time::Instant::now();
1075
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1076
+ let elapsed = start.elapsed();
1077
+
1078
+ assert!(result.is_err(), "{label}: should exceed time limit");
1079
+ let exc = result.unwrap_err();
1080
+ assert_eq!(
1081
+ exc.exc_type(),
1082
+ ExcType::TimeoutError,
1083
+ "{label}: expected TimeoutError, got: {exc}"
1084
+ );
1085
+ assert!(
1086
+ elapsed < Duration::from_secs(2),
1087
+ "{label}: should terminate promptly, took {elapsed:?}"
1088
+ );
1089
+ }
1090
+
1091
+ /// Test that `sum(range(huge))` respects the time limit.
1092
+ ///
1093
+ /// `sum()` iterates via `for_next()` which now calls `heap.check_time()`.
1094
+ #[test]
1095
+ fn timeout_in_sum_builtin() {
1096
+ assert_timeout_in_builtin("sum(range(10**18))", "sum(range(10**18))");
1097
+ }
1098
+
1099
+ /// Test that `list(range(huge))` respects the time limit.
1100
+ ///
1101
+ /// The `list()` constructor collects via `MontyIter::collect()` -> `for_next()`.
1102
+ #[test]
1103
+ fn timeout_in_list_constructor() {
1104
+ assert_timeout_in_builtin("list(range(10**18))", "list(range(10**18))");
1105
+ }
1106
+
1107
+ /// Test that `sorted(range(huge))` respects the time limit.
1108
+ ///
1109
+ /// `sorted()` first collects items via `for_next()`, then sorts. The collection
1110
+ /// phase alone should trigger the timeout for very large ranges.
1111
+ #[test]
1112
+ fn timeout_in_sorted_builtin() {
1113
+ assert_timeout_in_builtin("sorted(range(10**18))", "sorted(range(10**18))");
1114
+ }
1115
+
1116
+ /// Test that `min(range(huge))` respects the time limit.
1117
+ ///
1118
+ /// `min()` with a single iterable argument iterates via `for_next()`.
1119
+ #[test]
1120
+ fn timeout_in_min_builtin() {
1121
+ assert_timeout_in_builtin("min(range(10**18))", "min(range(10**18))");
1122
+ }
1123
+
1124
+ /// Test that `max(range(huge))` respects the time limit.
1125
+ ///
1126
+ /// `max()` with a single iterable argument iterates via `for_next()`.
1127
+ #[test]
1128
+ fn timeout_in_max_builtin() {
1129
+ assert_timeout_in_builtin("max(range(10**18))", "max(range(10**18))");
1130
+ }
1131
+
1132
+ /// Test that `all(range(huge))` respects the time limit.
1133
+ ///
1134
+ /// `all()` iterates via `for_next()` and only short-circuits on falsy values.
1135
+ /// `range(1, 10**18)` produces only truthy values so it keeps iterating.
1136
+ #[test]
1137
+ fn timeout_in_all_builtin() {
1138
+ assert_timeout_in_builtin("all(range(1, 10**18))", "all(range(1, 10**18))");
1139
+ }
1140
+
1141
+ /// Test that `enumerate(range(huge))` iteration respects the time limit.
1142
+ ///
1143
+ /// `enumerate()` creates tuples on each iteration via `for_next()`.
1144
+ #[test]
1145
+ #[cfg_attr(
1146
+ feature = "ref-count-panic",
1147
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1148
+ )]
1149
+ fn timeout_in_any_builtin() {
1150
+ // range(0, 1) repeated via a for loop calling any on each chunk isn't ideal,
1151
+ // but we can test with a large range starting from 0 where only first element is falsy
1152
+ // Actually, any(range(10**18)) will return True immediately because range starts at 0
1153
+ // which is falsy, but 1 is truthy. So any() returns True after checking 0, 1.
1154
+ // Instead, we need a different approach - just use the for_next timeout via enumerate.
1155
+ assert_timeout_in_builtin("list(enumerate(range(10**18)))", "enumerate(range(10**18))");
1156
+ }
1157
+
1158
+ /// Test that `tuple(range(huge))` respects the time limit.
1159
+ ///
1160
+ /// The `tuple()` constructor collects via `MontyIter::collect()` -> `for_next()`.
1161
+ #[test]
1162
+ fn timeout_in_tuple_constructor() {
1163
+ assert_timeout_in_builtin("tuple(range(10**18))", "tuple(range(10**18))");
1164
+ }
1165
+
1166
+ /// Test that `' '.join(...)` iteration respects the time limit.
1167
+ ///
1168
+ /// `str.join()` collects items from the iterable via `for_next()`.
1169
+ #[test]
1170
+ fn timeout_in_str_join() {
1171
+ assert_timeout_in_builtin("' '.join(str(i) for i in range(10**18))", "str.join with generator");
1172
+ }
1173
+
1174
+ /// Test that the insertion sort inner loop in `sorted()` respects the time limit.
1175
+ ///
1176
+ /// Uses reverse-sorted data to trigger worst-case O(n^2) insertion sort behavior.
1177
+ /// The sort comparison loop has an explicit `heap.check_time()` call.
1178
+ #[test]
1179
+ #[cfg_attr(
1180
+ feature = "ref-count-panic",
1181
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1182
+ )]
1183
+ fn timeout_in_sorted_comparison_loop() {
1184
+ // Build a reverse-sorted list, then sort it. Insertion sort on reverse-sorted
1185
+ // data is O(n^2).
1186
+ let code = r"
1187
+ x = list(range(10**6, 0, -1))
1188
+ sorted(x)
1189
+ ";
1190
+ assert_timeout_in_builtin(code, "sorted(reversed list)");
1191
+ }
1192
+
1193
+ /// Test that `[1] * 10_000_000` (list repetition) respects the time limit.
1194
+ ///
1195
+ /// The `mult_sequence()` copy loop now calls `heap.check_time()` on each
1196
+ /// repetition to prevent large sequence multiplications from bypassing timeout.
1197
+ #[test]
1198
+ #[cfg_attr(
1199
+ feature = "ref-count-panic",
1200
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1201
+ )]
1202
+ fn timeout_in_list_repetition() {
1203
+ assert_timeout_in_builtin("[1, 2, 3] * 10_000_000", "list repetition");
1204
+ }
1205
+
1206
+ /// Test that `(1,) * 10_000_000` (tuple repetition) respects the time limit.
1207
+ ///
1208
+ /// Same as list repetition but for tuples — both paths in `mult_sequence()`
1209
+ /// now check the time limit.
1210
+ #[test]
1211
+ #[cfg_attr(
1212
+ feature = "ref-count-panic",
1213
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1214
+ )]
1215
+ fn timeout_in_tuple_repetition() {
1216
+ assert_timeout_in_builtin("(1, 2, 3) * 10_000_000", "tuple repetition");
1217
+ }
1218
+
1219
+ /// Test that comparing two large equal lists respects the time limit.
1220
+ ///
1221
+ /// `List::py_eq()` iterates element-wise comparing pairs. With large equal lists,
1222
+ /// it must compare every element before returning True.
1223
+ #[test]
1224
+ #[cfg_attr(
1225
+ feature = "ref-count-panic",
1226
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1227
+ )]
1228
+ fn timeout_in_list_equality() {
1229
+ let code = r"
1230
+ a = list(range(10_000_000))
1231
+ b = list(range(10_000_000))
1232
+ a == b
1233
+ ";
1234
+ assert_timeout_in_builtin(code, "list equality");
1235
+ }
1236
+
1237
+ /// Test that comparing two large equal dicts respects the time limit.
1238
+ ///
1239
+ /// `Dict::py_eq()` iterates all entries checking keys and values. With large equal
1240
+ /// dicts, it must check every entry before returning True.
1241
+ #[test]
1242
+ #[cfg_attr(
1243
+ feature = "ref-count-panic",
1244
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1245
+ )]
1246
+ fn timeout_in_dict_equality() {
1247
+ let code = r"
1248
+ a = {i: i for i in range(10_000_000)}
1249
+ b = {i: i for i in range(10_000_000)}
1250
+ a == b
1251
+ ";
1252
+ assert_timeout_in_builtin(code, "dict equality");
1253
+ }
1254
+
1255
+ /// Test that `str.splitlines()` on a large string respects the time limit.
1256
+ ///
1257
+ /// `str_splitlines()` scans the entire string for line endings in a while loop
1258
+ /// that now calls `heap.check_time()` on each iteration.
1259
+ #[test]
1260
+ #[cfg_attr(
1261
+ feature = "ref-count-panic",
1262
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1263
+ )]
1264
+ fn timeout_in_str_splitlines() {
1265
+ let code = r"
1266
+ s = 'a\n' * 5_000_000
1267
+ s.splitlines()
1268
+ ";
1269
+ assert_timeout_in_builtin(code, "str.splitlines()");
1270
+ }
1271
+
1272
+ /// Test that `bytes.splitlines()` on large bytes respects the time limit.
1273
+ ///
1274
+ /// `bytes_splitlines()` scans bytes for line endings and now checks the time limit.
1275
+ #[test]
1276
+ #[cfg_attr(
1277
+ feature = "ref-count-panic",
1278
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1279
+ )]
1280
+ fn timeout_in_bytes_splitlines() {
1281
+ let code = r"
1282
+ s = b'a\n' * 5_000_000
1283
+ s.splitlines()
1284
+ ";
1285
+ assert_timeout_in_builtin(code, "bytes.splitlines()");
1286
+ }
1287
+
1288
+ // === Timeout truncation in repr ===
1289
+ // These tests verify that `repr()` on large containers respects the time limit
1290
+ // and terminates promptly instead of hanging indefinitely. The repr methods
1291
+ // (`repr_sequence_fmt`, `Dict::py_repr_fmt`, `SetInner::repr_fmt`) call
1292
+ // `heap.check_time()` on each iteration and write `...[timeout]` when the
1293
+ // time limit is exceeded, returning normally instead of propagating an error.
1294
+ //
1295
+ // Each test uses the external function "interrupt" pattern: the large object is
1296
+ // built with NO time limit, then execution pauses at `interrupt()`. A short time
1297
+ // limit is set before resuming, so only the `repr()` call is timed.
1298
+
1299
+ /// Helper: builds a large object without time limit, then runs `repr()` on it
1300
+ /// with a short time limit and asserts it produces a TimeoutError promptly.
1301
+ ///
1302
+ /// The code must call `interrupt()` between object construction and `repr()`.
1303
+ fn assert_repr_timeout(code: &str, label: &str) {
1304
+ let run = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1305
+
1306
+ // Phase 1: build the large object with no time limit
1307
+ let limits = ResourceLimits::new();
1308
+ let progress = run
1309
+ .start(vec![], LimitedTracker::new(limits), PrintWriter::Stdout)
1310
+ .unwrap();
1311
+ let mut call = resolve_name_lookups(progress)
1312
+ .unwrap()
1313
+ .into_function_call()
1314
+ .expect("interrupt call");
1315
+ assert_eq!(call.function_name, "interrupt");
1316
+
1317
+ // Phase 2: set a short time limit and resume — repr() should timeout
1318
+ call.tracker_mut().set_max_duration(Duration::from_millis(10));
1319
+
1320
+ let start = Instant::now();
1321
+ let result = call.resume(MontyObject::None, PrintWriter::Stdout);
1322
+ let elapsed = start.elapsed();
1323
+
1324
+ let exc = result.unwrap_err();
1325
+ assert_eq!(
1326
+ exc.exc_type(),
1327
+ ExcType::TimeoutError,
1328
+ "{label}: expected TimeoutError, got: {exc}"
1329
+ );
1330
+ let msg = exc.message().unwrap();
1331
+ assert!(msg.starts_with("time limit exceeded:"));
1332
+ assert!(msg.ends_with("ms > 10ms"));
1333
+ assert!(
1334
+ elapsed < Duration::from_millis(200),
1335
+ "{label}: should terminate promptly, took {elapsed:?}"
1336
+ );
1337
+ }
1338
+
1339
+ /// Test that `repr(large_list)` respects the time limit.
1340
+ ///
1341
+ /// Uses a list of 100K short strings so that repr formatting is slow enough
1342
+ /// to trigger the timeout.
1343
+ #[test]
1344
+ #[cfg_attr(
1345
+ feature = "ref-count-panic",
1346
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1347
+ )]
1348
+ fn timeout_truncation_in_list_repr() {
1349
+ let code = r"
1350
+ x = ['abcdefghij'] * 100_000
1351
+ interrupt()
1352
+ repr(x)
1353
+ ";
1354
+ assert_repr_timeout(code, "list repr");
1355
+ }
1356
+
1357
+ /// Test that `repr(large_dict)` respects the time limit.
1358
+ ///
1359
+ /// Uses a dict with 100K entries where values are short strings,
1360
+ /// making repr formatting slow enough to trigger the timeout.
1361
+ #[test]
1362
+ #[cfg_attr(
1363
+ feature = "ref-count-panic",
1364
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1365
+ )]
1366
+ fn timeout_truncation_in_dict_repr() {
1367
+ let code = r"
1368
+ x = {i: 'abcdefghij' for i in range(100_000)}
1369
+ interrupt()
1370
+ repr(x)
1371
+ ";
1372
+ assert_repr_timeout(code, "dict repr");
1373
+ }
1374
+
1375
+ /// Test that `repr(large_set)` respects the time limit.
1376
+ ///
1377
+ /// Uses a set of 100K unique strings so that repr formatting is slow enough
1378
+ /// to trigger the timeout.
1379
+ #[test]
1380
+ #[cfg_attr(
1381
+ feature = "ref-count-panic",
1382
+ ignore = "resource exhaustion doesn't guarantee heap state consistency"
1383
+ )]
1384
+ fn timeout_truncation_in_set_repr() {
1385
+ let code = r"
1386
+ x = {str(i) for i in range(100_000)}
1387
+ interrupt()
1388
+ repr(x)
1389
+ ";
1390
+ assert_repr_timeout(code, "set repr");
1391
+ }
1392
+
1393
+ /// Test that `str.replace` with amplification is rejected before allocation.
1394
+ ///
1395
+ /// `'a' * 1000` is 1KB (within limit), but replacing each 'a' with a 1KB string
1396
+ /// produces a 1MB result. The pre-check should reject this before `String::replace()`
1397
+ /// allocates the result on the Rust heap.
1398
+ #[test]
1399
+ fn str_replace_amplification_memory_limit() {
1400
+ let code = r"
1401
+ s = 'a' * 1000
1402
+ s.replace('a', 'b' * 1000)
1403
+ ";
1404
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1405
+
1406
+ let limits = ResourceLimits::new().max_memory(500_000); // 500KB limit
1407
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1408
+
1409
+ assert!(result.is_err(), "str.replace amplification should be rejected");
1410
+ let exc = result.unwrap_err();
1411
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1412
+ assert!(
1413
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
1414
+ "expected memory limit error, got: {exc}"
1415
+ );
1416
+ }
1417
+
1418
+ /// Test that small `str.replace` works within limits.
1419
+ #[test]
1420
+ fn str_replace_within_limit() {
1421
+ let code = "'hello world'.replace('world', 'rust') == 'hello rust'";
1422
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1423
+
1424
+ let limits = ResourceLimits::new().max_memory(100_000);
1425
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1426
+
1427
+ assert!(result.is_ok(), "small str.replace should succeed");
1428
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
1429
+ }
1430
+
1431
+ /// Test that `bytes.replace` with amplification is rejected before allocation.
1432
+ #[test]
1433
+ fn bytes_replace_amplification_memory_limit() {
1434
+ let code = r"
1435
+ s = b'a' * 1000
1436
+ s.replace(b'a', b'b' * 1000)
1437
+ ";
1438
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1439
+
1440
+ let limits = ResourceLimits::new().max_memory(500_000);
1441
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1442
+
1443
+ assert!(result.is_err(), "bytes.replace amplification should be rejected");
1444
+ let exc = result.unwrap_err();
1445
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1446
+ assert!(
1447
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
1448
+ "expected memory limit error, got: {exc}"
1449
+ );
1450
+ }
1451
+
1452
+ /// Test that `str.replace` with empty pattern amplification is rejected.
1453
+ ///
1454
+ /// Empty pattern inserts `new` before each char and after the last, so
1455
+ /// result size = input_len * (new_len + 1).
1456
+ #[test]
1457
+ fn str_replace_empty_pattern_memory_limit() {
1458
+ let code = r"
1459
+ s = 'a' * 500
1460
+ s.replace('', 'x' * 1000)
1461
+ ";
1462
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1463
+
1464
+ let limits = ResourceLimits::new().max_memory(200_000);
1465
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1466
+
1467
+ assert!(
1468
+ result.is_err(),
1469
+ "str.replace with empty pattern amplification should be rejected"
1470
+ );
1471
+ let exc = result.unwrap_err();
1472
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1473
+ }
1474
+
1475
+ /// Test that `str.ljust` with huge width is rejected before allocation.
1476
+ ///
1477
+ /// Without the pre-check, `String::with_capacity(width)` would allocate
1478
+ /// directly on the Rust heap, bypassing the memory tracker entirely.
1479
+ #[test]
1480
+ fn str_ljust_memory_limit() {
1481
+ let code = "'x'.ljust(2000000)";
1482
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1483
+
1484
+ let limits = ResourceLimits::new().max_memory(100_000);
1485
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1486
+
1487
+ assert!(result.is_err(), "str.ljust with huge width should be rejected");
1488
+ let exc = result.unwrap_err();
1489
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1490
+ assert!(
1491
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
1492
+ "expected memory limit error, got: {exc}"
1493
+ );
1494
+ }
1495
+
1496
+ /// Test that `str.rjust` with huge width is rejected before allocation.
1497
+ #[test]
1498
+ fn str_rjust_memory_limit() {
1499
+ let code = "'x'.rjust(2000000)";
1500
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1501
+
1502
+ let limits = ResourceLimits::new().max_memory(100_000);
1503
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1504
+
1505
+ assert!(result.is_err(), "str.rjust with huge width should be rejected");
1506
+ let exc = result.unwrap_err();
1507
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1508
+ }
1509
+
1510
+ /// Test that `str.center` with huge width is rejected before allocation.
1511
+ #[test]
1512
+ fn str_center_memory_limit() {
1513
+ let code = "'x'.center(2000000)";
1514
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1515
+
1516
+ let limits = ResourceLimits::new().max_memory(100_000);
1517
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1518
+
1519
+ assert!(result.is_err(), "str.center with huge width should be rejected");
1520
+ let exc = result.unwrap_err();
1521
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1522
+ }
1523
+
1524
+ /// Test that `str.zfill` with huge width is rejected before allocation.
1525
+ #[test]
1526
+ fn str_zfill_memory_limit() {
1527
+ let code = "'42'.zfill(2000000)";
1528
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1529
+
1530
+ let limits = ResourceLimits::new().max_memory(100_000);
1531
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1532
+
1533
+ assert!(result.is_err(), "str.zfill with huge width should be rejected");
1534
+ let exc = result.unwrap_err();
1535
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1536
+ }
1537
+
1538
+ /// Test that small padding operations work within limits.
1539
+ #[test]
1540
+ fn str_padding_within_limit() {
1541
+ let code = "'hi'.ljust(10) == 'hi '";
1542
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1543
+
1544
+ let limits = ResourceLimits::new().max_memory(100_000);
1545
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1546
+
1547
+ assert!(result.is_ok(), "small padding should succeed");
1548
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
1549
+ }
1550
+
1551
+ /// Test that `bytes.ljust` with huge width is rejected before allocation.
1552
+ #[test]
1553
+ fn bytes_ljust_memory_limit() {
1554
+ let code = "b'x'.ljust(2000000)";
1555
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1556
+
1557
+ let limits = ResourceLimits::new().max_memory(100_000);
1558
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1559
+
1560
+ assert!(result.is_err(), "bytes.ljust with huge width should be rejected");
1561
+ let exc = result.unwrap_err();
1562
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1563
+ }
1564
+
1565
+ /// Test that `bytes.rjust` with huge width is rejected before allocation.
1566
+ #[test]
1567
+ fn bytes_rjust_memory_limit() {
1568
+ let code = "b'x'.rjust(2000000)";
1569
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1570
+
1571
+ let limits = ResourceLimits::new().max_memory(100_000);
1572
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1573
+
1574
+ assert!(result.is_err(), "bytes.rjust with huge width should be rejected");
1575
+ let exc = result.unwrap_err();
1576
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1577
+ }
1578
+
1579
+ /// Test that `bytes.center` with huge width is rejected before allocation.
1580
+ #[test]
1581
+ fn bytes_center_memory_limit() {
1582
+ let code = "b'x'.center(2000000)";
1583
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1584
+
1585
+ let limits = ResourceLimits::new().max_memory(100_000);
1586
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1587
+
1588
+ assert!(result.is_err(), "bytes.center with huge width should be rejected");
1589
+ let exc = result.unwrap_err();
1590
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1591
+ }
1592
+
1593
+ /// Test that `bytes.zfill` with huge width is rejected before allocation.
1594
+ #[test]
1595
+ fn bytes_zfill_memory_limit() {
1596
+ let code = "b'42'.zfill(2000000)";
1597
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1598
+
1599
+ let limits = ResourceLimits::new().max_memory(100_000);
1600
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1601
+
1602
+ assert!(result.is_err(), "bytes.zfill with huge width should be rejected");
1603
+ let exc = result.unwrap_err();
1604
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1605
+ }
1606
+
1607
+ /// Test that f-string formatting with huge width is rejected before allocation.
1608
+ #[test]
1609
+ fn fstring_dynamic_width_memory_limit() {
1610
+ // Dynamic format spec via f-string nesting: {w} produces a runtime-parsed spec
1611
+ let code = "w = 2000000\nf\"{'x':>{w}}\"";
1612
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1613
+
1614
+ let limits = ResourceLimits::new().max_memory(100_000);
1615
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1616
+
1617
+ assert!(result.is_err(), "f-string with huge dynamic width should be rejected");
1618
+ let exc = result.unwrap_err();
1619
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1620
+ }
1621
+
1622
+ // === re.sub() memory tracking tests ===
1623
+ // These tests verify that the single-pass replacement loop in `re.sub()` tracks
1624
+ // the running output size and bails out when the resource limit is exceeded.
1625
+
1626
+ /// Test that `re.sub` with every-char pattern amplification is rejected.
1627
+ ///
1628
+ /// Pattern 'a' matches every character in 'aaa...'. Each replacement expands
1629
+ /// 1 byte → 1000 bytes, so the output grows to ~1MB which exceeds the 500KB limit.
1630
+ /// The inline loop catches this after a few hundred matches.
1631
+ #[test]
1632
+ fn re_sub_amplification_memory_limit() {
1633
+ let code = r"
1634
+ import re
1635
+ s = 'a' * 1000
1636
+ re.sub('a', 'b' * 1000, s)
1637
+ ";
1638
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1639
+
1640
+ let limits = ResourceLimits::new().max_memory(500_000);
1641
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1642
+
1643
+ assert!(result.is_err(), "re.sub amplification should be rejected");
1644
+ let exc = result.unwrap_err();
1645
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1646
+ assert!(
1647
+ exc.message().is_some_and(|m| m.contains("memory limit exceeded")),
1648
+ "expected memory limit error, got: {exc}"
1649
+ );
1650
+ }
1651
+
1652
+ /// Test that `re.sub` with empty pattern amplification is rejected.
1653
+ ///
1654
+ /// Empty pattern matches N+1 times for N-char input (between and around every
1655
+ /// character). Each match inserts 1000 bytes, so 501 matches × 1000 ≈ 500KB
1656
+ /// which exceeds the 200KB limit.
1657
+ #[test]
1658
+ fn re_sub_empty_pattern_amplification_memory_limit() {
1659
+ let code = r"
1660
+ import re
1661
+ s = 'a' * 500
1662
+ re.sub('', 'x' * 1000, s)
1663
+ ";
1664
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1665
+
1666
+ let limits = ResourceLimits::new().max_memory(200_000);
1667
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1668
+
1669
+ assert!(
1670
+ result.is_err(),
1671
+ "re.sub with empty pattern amplification should be rejected"
1672
+ );
1673
+ let exc = result.unwrap_err();
1674
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1675
+ }
1676
+
1677
+ /// Test that `pattern.sub` (compiled pattern method) is also rejected.
1678
+ #[test]
1679
+ fn re_pattern_sub_amplification_memory_limit() {
1680
+ let code = r"
1681
+ import re
1682
+ p = re.compile('a')
1683
+ s = 'a' * 1000
1684
+ p.sub('b' * 1000, s)
1685
+ ";
1686
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1687
+
1688
+ let limits = ResourceLimits::new().max_memory(500_000);
1689
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1690
+
1691
+ assert!(result.is_err(), "pattern.sub amplification should be rejected");
1692
+ let exc = result.unwrap_err();
1693
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1694
+ }
1695
+
1696
+ /// Test that `re.sub` raises `re.PatternError` when the regex engine hits its backtracking limit.
1697
+ ///
1698
+ /// The pattern `(a+)+\1b` forces `fancy_regex` into its backtracking VM (due to the
1699
+ /// backreference `\1`). With enough `a`s followed by a non-matching character, the
1700
+ /// exponential blowup exceeds the engine's backtracking step limit (~1M steps).
1701
+ #[test]
1702
+ fn re_sub_backtracking_limit_raises_pattern_error() {
1703
+ let code = r"
1704
+ import re
1705
+ re.sub('(a+)+\\1b', 'X', 'a' * 30 + 'c')
1706
+ ";
1707
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1708
+
1709
+ let limits = ResourceLimits::new().max_memory(500_000);
1710
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1711
+
1712
+ assert!(result.is_err(), "backtracking limit should raise an error");
1713
+ let exc = result.unwrap_err();
1714
+ assert_eq!(exc.exc_type(), ExcType::RePatternError);
1715
+ assert!(
1716
+ exc.message().is_some_and(|m| m.contains("backtrack")),
1717
+ "expected backtracking error, got: {exc}"
1718
+ );
1719
+ }
1720
+
1721
+ // --- Selective patterns: few matches in large text stay within limits ---
1722
+
1723
+ /// Test that a selective pattern on large text passes.
1724
+ ///
1725
+ /// The pattern `xxx` only matches 3 times (at positions 0, 3, 6 in the 9-char prefix),
1726
+ /// so the result is ~10000 - 9 + 300 = 10291 bytes — well within the 500KB limit.
1727
+ #[test]
1728
+ fn re_sub_selective_pattern_passes() {
1729
+ // 'xxx' repeated 3 times at the start, rest is 'a's
1730
+ let code = r"
1731
+ import re
1732
+ s = 'xxx' * 3 + 'a' * 9991
1733
+ result = re.sub('xxx', 'y' * 100, s)
1734
+ len(result) == 9991 + 3 * 100
1735
+ ";
1736
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1737
+
1738
+ let limits = ResourceLimits::new().max_memory(500_000);
1739
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1740
+
1741
+ assert!(
1742
+ result.is_ok(),
1743
+ "selective pattern with few matches should pass: {result:?}"
1744
+ );
1745
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
1746
+ }
1747
+
1748
+ /// Test that a digit-matching pattern on mostly-text input passes.
1749
+ ///
1750
+ /// Pattern `\d+` matches only the 10-digit number, so the result is
1751
+ /// 990 + 200 = 1190 bytes — well within the 150KB limit.
1752
+ #[test]
1753
+ fn re_sub_digit_pattern_passes() {
1754
+ let code = r"
1755
+ import re
1756
+ s = 'a' * 990 + '1234567890'
1757
+ result = re.sub('\d+', 'X' * 200, s)
1758
+ len(result) == 990 + 200
1759
+ ";
1760
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1761
+
1762
+ let limits = ResourceLimits::new().max_memory(150_000);
1763
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1764
+
1765
+ assert!(result.is_ok(), "digit pattern on mostly-text should pass: {result:?}");
1766
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
1767
+ }
1768
+
1769
+ /// Test that every-char amplification is still rejected even with a generic pattern.
1770
+ ///
1771
+ /// Pattern `.` matches every character (10000 matches), each expanding 1 → 1000 bytes.
1772
+ /// The inline loop catches this after a few hundred matches once the running output
1773
+ /// size exceeds the 500KB limit.
1774
+ #[test]
1775
+ fn re_sub_every_char_amplification_rejected() {
1776
+ let code = r"
1777
+ import re
1778
+ s = 'a' * 10000
1779
+ re.sub('.', 'b' * 1000, s)
1780
+ ";
1781
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1782
+
1783
+ let limits = ResourceLimits::new().max_memory(500_000);
1784
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1785
+
1786
+ assert!(result.is_err(), "every-char pattern amplification should be rejected");
1787
+ let exc = result.unwrap_err();
1788
+ assert_eq!(exc.exc_type(), ExcType::MemoryError);
1789
+ }
1790
+
1791
+ // --- General re.sub tests ---
1792
+
1793
+ /// Test that small `re.sub` works within limits.
1794
+ #[test]
1795
+ fn re_sub_within_limit() {
1796
+ let code = r"
1797
+ import re
1798
+ re.sub('world', 'rust', 'hello world') == 'hello rust'
1799
+ ";
1800
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1801
+
1802
+ let limits = ResourceLimits::new().max_memory(100_000);
1803
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1804
+
1805
+ assert!(result.is_ok(), "small re.sub should succeed");
1806
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
1807
+ }
1808
+
1809
+ /// Test that `re.sub` with count parameter limits replacements correctly.
1810
+ ///
1811
+ /// `count=5` caps replacements to 5, so the result is
1812
+ /// 995 unchanged bytes + 5 × 100 replacement bytes = 1495 bytes.
1813
+ #[test]
1814
+ fn re_sub_with_count_within_limit() {
1815
+ let code = r"
1816
+ import re
1817
+ re.sub('a', 'b' * 100, 'a' * 1000, count=5) == 'b' * 500 + 'a' * 995
1818
+ ";
1819
+ let ex = MontyRun::new(code.to_owned(), "test.py", vec![]).unwrap();
1820
+
1821
+ let limits = ResourceLimits::new().max_memory(500_000);
1822
+ let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1823
+
1824
+ assert!(result.is_ok(), "re.sub with small count should succeed");
1825
+ assert_eq!(result.unwrap(), MontyObject::Bool(true));
1826
+ }