superacli 1.1.5 → 1.1.7

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 (1081) hide show
  1. package/README.md +77 -53
  2. package/__tests__/azd-plugin.test.js +109 -0
  3. package/__tests__/config.test.js +4 -3
  4. package/__tests__/discover.test.js +59 -0
  5. package/__tests__/goose-plugin.test.js +149 -0
  6. package/__tests__/help-json.test.js +2 -0
  7. package/__tests__/openhands-plugin.test.js +106 -0
  8. package/__tests__/plugin-cocoindex-code-uninstall.test.js +19 -0
  9. package/__tests__/plugin-cocoindex-code.test.js +37 -0
  10. package/__tests__/plugin-install-guidance.test.js +81 -0
  11. package/__tests__/plugins-registry.test.js +44 -0
  12. package/__tests__/plugins-store.test.js +40 -5
  13. package/__tests__/process-adapter.test.js +50 -1
  14. package/__tests__/resend-plugin.test.js +22 -1
  15. package/__tests__/server-app.test.js +1 -0
  16. package/__tests__/server-routes-commands.test.js +20 -2
  17. package/__tests__/server-routes-plugins.test.js +130 -0
  18. package/__tests__/skills.test.js +26 -0
  19. package/__tests__/squirrelscan-plugin.test.js +129 -0
  20. package/__tests__/uipath-plugin.test.js +104 -0
  21. package/__tests__/uipathcli-plugin.test.js +95 -0
  22. package/cli/adapters/mcp.js +2 -0
  23. package/cli/adapters/process.js +49 -2
  24. package/cli/config.js +240 -3
  25. package/cli/discover.js +157 -0
  26. package/cli/help-json.js +16 -1
  27. package/cli/plugin-install-guidance.js +92 -37
  28. package/cli/plugins-manager.js +1 -0
  29. package/cli/plugins-registry.js +74 -8
  30. package/cli/plugins-store.js +78 -17
  31. package/cli/skills-mcp.js +1 -1
  32. package/cli/skills.js +39 -2
  33. package/cli/supercli.js +87 -11
  34. package/docs/feature-gaps.md +8 -8
  35. package/docs/features/azd-uipath-plugins.md +43 -0
  36. package/docs/features/server-plugins.md +62 -0
  37. package/docs/features/skills.md +9 -5
  38. package/docs/{supported-harnesses.md → plugins-available.md} +4 -3
  39. package/docs/{plugin-harness-guide.md → plugins-how-to.md} +1 -1
  40. package/docs/plugins.md +26 -20
  41. package/docs/server-plugins-usage-guide.md +182 -0
  42. package/docs/skills-catalog.md +12 -10
  43. package/package.json +1 -1
  44. package/plugins/agent-browser/README.md +69 -0
  45. package/plugins/agent-browser/plugin.json +111 -0
  46. package/plugins/agent-browser/skills/quickstart/SKILL.md +66 -0
  47. package/plugins/aider/README.md +53 -0
  48. package/plugins/aider/plugin.json +105 -0
  49. package/plugins/aider/scripts/aider-wrapper.js +243 -0
  50. package/plugins/aider/scripts/setup-aider.js +37 -0
  51. package/plugins/aider/skills/dry-run-review.md +24 -0
  52. package/plugins/aider/skills/model-and-provider.md +24 -0
  53. package/plugins/aider/skills/one-shot-edits.md +30 -0
  54. package/plugins/aider/skills/quickstart/SKILL.md +51 -0
  55. package/plugins/azd/README.md +28 -0
  56. package/plugins/azd/plugin.json +87 -0
  57. package/plugins/azd/skills/quickstart/SKILL.md +41 -0
  58. package/plugins/blogwatcher/README.md +3 -3
  59. package/plugins/boxlite/Dockerfile +9 -0
  60. package/plugins/boxlite/README.md +62 -0
  61. package/plugins/boxlite/plugin.json +201 -0
  62. package/plugins/boxlite/scripts/run-boxlite.js +106 -0
  63. package/plugins/boxlite/skills/quickstart/SKILL.md +40 -0
  64. package/plugins/cass/plugin.json +150 -0
  65. package/plugins/cass/scripts/setup-cass.js +47 -0
  66. package/plugins/cass/skills/quickstart/SKILL.md +46 -0
  67. package/plugins/clever/README.md +46 -0
  68. package/plugins/clever/plugin.json +119 -0
  69. package/plugins/clever/scripts/setup-clever.js +28 -0
  70. package/plugins/clever/skills/auth-and-profile.md +29 -0
  71. package/plugins/clever/skills/passthrough-safety.md +21 -0
  72. package/plugins/clever/skills/quickstart/SKILL.md +45 -0
  73. package/plugins/clever/skills/resource-inventory.md +24 -0
  74. package/plugins/clix/README.md +4 -4
  75. package/plugins/cocoindex-code/README.md +64 -0
  76. package/plugins/cocoindex-code/plugin.json +81 -0
  77. package/plugins/cocoindex-code/scripts/__pycache__/query.cpython-310.pyc +0 -0
  78. package/plugins/cocoindex-code/scripts/__pycache__/query.cpython-311.pyc +0 -0
  79. package/plugins/cocoindex-code/scripts/post-install.js +61 -0
  80. package/plugins/cocoindex-code/scripts/post-uninstall.js +25 -0
  81. package/plugins/cocoindex-code/scripts/query.py +88 -0
  82. package/plugins/cocoindex-code/scripts/run-query.js +50 -0
  83. package/plugins/cocoindex-code/skills/quickstart/SKILL.md +73 -0
  84. package/plugins/copilot/README.md +24 -0
  85. package/plugins/copilot/plugin.json +80 -0
  86. package/plugins/copilot/skills/quickstart/SKILL.md +44 -0
  87. package/plugins/gemini/README.md +24 -0
  88. package/plugins/gemini/plugin.json +98 -0
  89. package/plugins/gemini/skills/quickstart/SKILL.md +44 -0
  90. package/plugins/gifcap/plugin.json +119 -0
  91. package/plugins/gifcap/scripts/setup-gifcap.js +44 -0
  92. package/plugins/gifcap/skills/quickstart/SKILL.md +34 -0
  93. package/plugins/gifcap/test-record-quiet.gif +0 -0
  94. package/plugins/gifcap/test-record.gif +0 -0
  95. package/plugins/goose/README.md +36 -0
  96. package/plugins/goose/plugin.json +183 -0
  97. package/plugins/goose/skills/quickstart/SKILL.md +44 -0
  98. package/plugins/json-server/README.md +58 -0
  99. package/plugins/json-server/plugin.json +113 -0
  100. package/plugins/json-server/skills/quickstart/SKILL.md +57 -0
  101. package/plugins/lightpanda/README.md +145 -0
  102. package/plugins/lightpanda/package-lock.json +1375 -0
  103. package/plugins/lightpanda/package.json +12 -0
  104. package/plugins/lightpanda/plugin.json +116 -0
  105. package/plugins/lightpanda/scripts/lightpanda-contacts.js +494 -0
  106. package/plugins/lightpanda/scripts/lightpanda-generic-extract.js +403 -0
  107. package/plugins/lightpanda/scripts/lightpanda-wrapper.js +480 -0
  108. package/plugins/lightpanda/scripts/setup-lightpanda.js +39 -0
  109. package/plugins/lightpanda/skills/contact-discovery.md +51 -0
  110. package/plugins/lightpanda/skills/generic-extraction.md +66 -0
  111. package/plugins/lightpanda/skills/quickstart/SKILL.md +103 -0
  112. package/plugins/lightpanda/skills/resilient-navigation.md +42 -0
  113. package/plugins/monty/README.md +2 -2
  114. package/plugins/nullclaw/README.md +3 -3
  115. package/plugins/offline-ai/README.md +23 -0
  116. package/plugins/offline-ai/plugin.json +82 -0
  117. package/plugins/offline-ai/skills/quickstart/SKILL.md +43 -0
  118. package/plugins/openhands/README.md +25 -0
  119. package/plugins/openhands/plugin.json +116 -0
  120. package/plugins/openhands/skills/quickstart/SKILL.md +26 -0
  121. package/plugins/plandex/README.md +25 -0
  122. package/plugins/plandex/plugin.json +130 -0
  123. package/plugins/plandex/skills/quickstart/SKILL.md +50 -0
  124. package/plugins/plugins.json +190 -2
  125. package/plugins/resend/plugin.json +279 -2
  126. package/plugins/resend/skills/quickstart/SKILL.md +32 -13
  127. package/plugins/squirrelscan/Dockerfile +5 -0
  128. package/plugins/squirrelscan/README.md +47 -0
  129. package/plugins/squirrelscan/plugin.json +493 -0
  130. package/plugins/squirrelscan/scripts/post-install.js +33 -0
  131. package/plugins/squirrelscan/scripts/post-uninstall.js +25 -0
  132. package/plugins/squirrelscan/scripts/run-squirrel.js +73 -0
  133. package/plugins/squirrelscan/skills/audit-workflow/SKILL.md +33 -0
  134. package/plugins/squirrelscan/skills/publish-report/SKILL.md +33 -0
  135. package/plugins/squirrelscan/skills/quickstart/SKILL.md +41 -0
  136. package/plugins/uipath/README.md +27 -0
  137. package/plugins/uipath/plugin.json +86 -0
  138. package/plugins/uipath/skills/quickstart/SKILL.md +47 -0
  139. package/plugins/uipathcli/README.md +28 -0
  140. package/plugins/uipathcli/plugin.json +120 -0
  141. package/plugins/uipathcli/scripts/run-uipath-cli.js +49 -0
  142. package/plugins/uipathcli/skills/quickstart/SKILL.md +22 -0
  143. package/plugins/xurl/README.md +4 -4
  144. package/server/app.js +5 -2
  145. package/server/public/app.js +3 -0
  146. package/server/routes/commands.js +95 -12
  147. package/server/routes/plugins.js +262 -0
  148. package/server/services/pluginsService.js +303 -0
  149. package/server/views/command-edit.ejs +196 -14
  150. package/server/views/partials/head.ejs +1 -0
  151. package/server/views/plugins.ejs +264 -0
  152. package/tests/test-plugins-registry.js +30 -0
  153. package/tests/test-resend-smoke.sh +7 -3
  154. package/.beads/.br_history/issues.20260308_200823_636718328.jsonl +0 -20
  155. package/.beads/.br_history/issues.20260308_200823_636718328.jsonl.meta.json +0 -1
  156. package/.beads/.br_history/issues.20260308_200827_033159453.jsonl +0 -21
  157. package/.beads/.br_history/issues.20260308_200827_033159453.jsonl.meta.json +0 -1
  158. package/.beads/.br_history/issues.20260308_200829_595900053.jsonl +0 -22
  159. package/.beads/.br_history/issues.20260308_200829_595900053.jsonl.meta.json +0 -1
  160. package/.beads/.br_history/issues.20260308_200834_079930100.jsonl +0 -23
  161. package/.beads/.br_history/issues.20260308_200834_079930100.jsonl.meta.json +0 -1
  162. package/.beads/.br_history/issues.20260308_200858_370924996.jsonl +0 -24
  163. package/.beads/.br_history/issues.20260308_200858_370924996.jsonl.meta.json +0 -1
  164. package/.beads/.br_history/issues.20260308_201031_019730855.jsonl +0 -24
  165. package/.beads/.br_history/issues.20260308_201031_019730855.jsonl.meta.json +0 -1
  166. package/.beads/.br_history/issues.20260308_201031_578974884.jsonl +0 -24
  167. package/.beads/.br_history/issues.20260308_201031_578974884.jsonl.meta.json +0 -1
  168. package/.beads/.br_history/issues.20260308_201054_780345548.jsonl +0 -24
  169. package/.beads/.br_history/issues.20260308_201054_780345548.jsonl.meta.json +0 -1
  170. package/.beads/.br_history/issues.20260308_201054_896980019.jsonl +0 -24
  171. package/.beads/.br_history/issues.20260308_201054_896980019.jsonl.meta.json +0 -1
  172. package/.beads/.br_history/issues.20260308_201128_599819688.jsonl +0 -24
  173. package/.beads/.br_history/issues.20260308_201128_599819688.jsonl.meta.json +0 -1
  174. package/.beads/.br_history/issues.20260308_201128_710221699.jsonl +0 -24
  175. package/.beads/.br_history/issues.20260308_201128_710221699.jsonl.meta.json +0 -1
  176. package/.beads/.br_history/issues.20260308_201204_745649213.jsonl +0 -24
  177. package/.beads/.br_history/issues.20260308_201204_745649213.jsonl.meta.json +0 -1
  178. package/.beads/.br_history/issues.20260308_201338_908436144.jsonl +0 -24
  179. package/.beads/.br_history/issues.20260308_201338_908436144.jsonl.meta.json +0 -1
  180. package/.beads/.br_history/issues.20260308_201344_734860714.jsonl +0 -25
  181. package/.beads/.br_history/issues.20260308_201344_734860714.jsonl.meta.json +0 -1
  182. package/.beads/.br_history/issues.20260308_201630_819282295.jsonl +0 -25
  183. package/.beads/.br_history/issues.20260308_201630_819282295.jsonl.meta.json +0 -1
  184. package/.beads/.br_history/issues.20260308_203538_054279699.jsonl +0 -25
  185. package/.beads/.br_history/issues.20260308_203538_054279699.jsonl.meta.json +0 -1
  186. package/.beads/.br_history/issues.20260308_203547_597113070.jsonl +0 -26
  187. package/.beads/.br_history/issues.20260308_203547_597113070.jsonl.meta.json +0 -1
  188. package/.beads/.br_history/issues.20260308_203547_775139216.jsonl +0 -27
  189. package/.beads/.br_history/issues.20260308_203547_775139216.jsonl.meta.json +0 -1
  190. package/.beads/.br_history/issues.20260308_203547_950724773.jsonl +0 -28
  191. package/.beads/.br_history/issues.20260308_203547_950724773.jsonl.meta.json +0 -1
  192. package/.beads/.br_history/issues.20260308_203548_107684523.jsonl +0 -29
  193. package/.beads/.br_history/issues.20260308_203548_107684523.jsonl.meta.json +0 -1
  194. package/.beads/.br_history/issues.20260308_203548_310389993.jsonl +0 -30
  195. package/.beads/.br_history/issues.20260308_203548_310389993.jsonl.meta.json +0 -1
  196. package/.beads/.br_history/issues.20260308_203825_953337320.jsonl +0 -31
  197. package/.beads/.br_history/issues.20260308_203825_953337320.jsonl.meta.json +0 -1
  198. package/.beads/.br_history/issues.20260308_204056_071377736.jsonl +0 -32
  199. package/.beads/.br_history/issues.20260308_204056_071377736.jsonl.meta.json +0 -1
  200. package/.beads/.br_history/issues.20260308_205141_517616844.jsonl +0 -32
  201. package/.beads/.br_history/issues.20260308_205141_517616844.jsonl.meta.json +0 -1
  202. package/.beads/.br_history/issues.20260308_205141_648994024.jsonl +0 -32
  203. package/.beads/.br_history/issues.20260308_205141_648994024.jsonl.meta.json +0 -1
  204. package/.beads/.br_history/issues.20260308_205141_867598036.jsonl +0 -32
  205. package/.beads/.br_history/issues.20260308_205141_867598036.jsonl.meta.json +0 -1
  206. package/.beads/.br_history/issues.20260308_205142_094157355.jsonl +0 -32
  207. package/.beads/.br_history/issues.20260308_205142_094157355.jsonl.meta.json +0 -1
  208. package/.beads/.br_history/issues.20260308_205142_327315677.jsonl +0 -32
  209. package/.beads/.br_history/issues.20260308_205142_327315677.jsonl.meta.json +0 -1
  210. package/.beads/.br_history/issues.20260308_205142_545563822.jsonl +0 -32
  211. package/.beads/.br_history/issues.20260308_205142_545563822.jsonl.meta.json +0 -1
  212. package/.beads/.br_history/issues.20260308_205213_061989333.jsonl +0 -32
  213. package/.beads/.br_history/issues.20260308_205213_061989333.jsonl.meta.json +0 -1
  214. package/.beads/.br_history/issues.20260308_205213_181103364.jsonl +0 -32
  215. package/.beads/.br_history/issues.20260308_205213_181103364.jsonl.meta.json +0 -1
  216. package/.beads/.br_history/issues.20260308_205213_408872234.jsonl +0 -32
  217. package/.beads/.br_history/issues.20260308_205213_408872234.jsonl.meta.json +0 -1
  218. package/.beads/.br_history/issues.20260308_205213_616681652.jsonl +0 -32
  219. package/.beads/.br_history/issues.20260308_205213_616681652.jsonl.meta.json +0 -1
  220. package/.beads/.br_history/issues.20260308_205213_821507069.jsonl +0 -32
  221. package/.beads/.br_history/issues.20260308_205213_821507069.jsonl.meta.json +0 -1
  222. package/.beads/.br_history/issues.20260308_205214_026661112.jsonl +0 -32
  223. package/.beads/.br_history/issues.20260308_205214_026661112.jsonl.meta.json +0 -1
  224. package/.beads/.br_history/issues.20260308_205454_955250554.jsonl +0 -32
  225. package/.beads/.br_history/issues.20260308_205454_955250554.jsonl.meta.json +0 -1
  226. package/.beads/.br_history/issues.20260308_205556_337800392.jsonl +0 -33
  227. package/.beads/.br_history/issues.20260308_205556_337800392.jsonl.meta.json +0 -1
  228. package/.beads/.br_history/issues.20260308_205824_274686694.jsonl +0 -33
  229. package/.beads/.br_history/issues.20260308_205824_274686694.jsonl.meta.json +0 -1
  230. package/.beads/.br_history/issues.20260308_210240_583768328.jsonl +0 -34
  231. package/.beads/.br_history/issues.20260308_210240_583768328.jsonl.meta.json +0 -1
  232. package/.beads/.br_history/issues.20260308_212223_641541494.jsonl +0 -34
  233. package/.beads/.br_history/issues.20260308_212223_641541494.jsonl.meta.json +0 -1
  234. package/.beads/.br_history/issues.20260308_212227_735550996.jsonl +0 -35
  235. package/.beads/.br_history/issues.20260308_212227_735550996.jsonl.meta.json +0 -1
  236. package/.beads/.br_history/issues.20260308_212232_547298548.jsonl +0 -36
  237. package/.beads/.br_history/issues.20260308_212232_547298548.jsonl.meta.json +0 -1
  238. package/.beads/.br_history/issues.20260308_212528_843628125.jsonl +0 -37
  239. package/.beads/.br_history/issues.20260308_212528_843628125.jsonl.meta.json +0 -1
  240. package/.beads/.br_history/issues.20260308_212529_094530502.jsonl +0 -38
  241. package/.beads/.br_history/issues.20260308_212529_094530502.jsonl.meta.json +0 -1
  242. package/.beads/.br_history/issues.20260308_212529_331000853.jsonl +0 -39
  243. package/.beads/.br_history/issues.20260308_212529_331000853.jsonl.meta.json +0 -1
  244. package/.beads/.br_history/issues.20260308_212529_587925652.jsonl +0 -40
  245. package/.beads/.br_history/issues.20260308_212529_587925652.jsonl.meta.json +0 -1
  246. package/.beads/.br_history/issues.20260308_212804_927764103.jsonl +0 -41
  247. package/.beads/.br_history/issues.20260308_212804_927764103.jsonl.meta.json +0 -1
  248. package/.beads/.br_history/issues.20260308_212805_153673453.jsonl +0 -42
  249. package/.beads/.br_history/issues.20260308_212805_153673453.jsonl.meta.json +0 -1
  250. package/.beads/.br_history/issues.20260308_212805_415982363.jsonl +0 -43
  251. package/.beads/.br_history/issues.20260308_212805_415982363.jsonl.meta.json +0 -1
  252. package/.beads/.br_history/issues.20260308_212805_657497741.jsonl +0 -44
  253. package/.beads/.br_history/issues.20260308_212805_657497741.jsonl.meta.json +0 -1
  254. package/.beads/.br_history/issues.20260308_212805_952838724.jsonl +0 -45
  255. package/.beads/.br_history/issues.20260308_212805_952838724.jsonl.meta.json +0 -1
  256. package/.beads/.br_history/issues.20260308_212806_325433779.jsonl +0 -46
  257. package/.beads/.br_history/issues.20260308_212806_325433779.jsonl.meta.json +0 -1
  258. package/.beads/.br_history/issues.20260308_212806_584685598.jsonl +0 -47
  259. package/.beads/.br_history/issues.20260308_212806_584685598.jsonl.meta.json +0 -1
  260. package/.beads/.br_history/issues.20260308_212806_827817208.jsonl +0 -48
  261. package/.beads/.br_history/issues.20260308_212806_827817208.jsonl.meta.json +0 -1
  262. package/.beads/.br_history/issues.20260308_212807_111320451.jsonl +0 -49
  263. package/.beads/.br_history/issues.20260308_212807_111320451.jsonl.meta.json +0 -1
  264. package/.beads/.br_history/issues.20260308_212807_409545536.jsonl +0 -50
  265. package/.beads/.br_history/issues.20260308_212807_409545536.jsonl.meta.json +0 -1
  266. package/.beads/.br_history/issues.20260308_212807_625063294.jsonl +0 -51
  267. package/.beads/.br_history/issues.20260308_212807_625063294.jsonl.meta.json +0 -1
  268. package/.beads/.br_history/issues.20260308_212807_843906551.jsonl +0 -52
  269. package/.beads/.br_history/issues.20260308_212807_843906551.jsonl.meta.json +0 -1
  270. package/.beads/.br_history/issues.20260308_212808_100304073.jsonl +0 -53
  271. package/.beads/.br_history/issues.20260308_212808_100304073.jsonl.meta.json +0 -1
  272. package/.beads/.br_history/issues.20260308_212808_324723976.jsonl +0 -54
  273. package/.beads/.br_history/issues.20260308_212808_324723976.jsonl.meta.json +0 -1
  274. package/.beads/.br_history/issues.20260308_212808_557513104.jsonl +0 -55
  275. package/.beads/.br_history/issues.20260308_212808_557513104.jsonl.meta.json +0 -1
  276. package/.beads/.br_history/issues.20260308_212808_788048322.jsonl +0 -56
  277. package/.beads/.br_history/issues.20260308_212808_788048322.jsonl.meta.json +0 -1
  278. package/.beads/.br_history/issues.20260308_213702_613249728.jsonl +0 -57
  279. package/.beads/.br_history/issues.20260308_213702_613249728.jsonl.meta.json +0 -1
  280. package/.beads/.br_history/issues.20260308_213715_115792063.jsonl +0 -57
  281. package/.beads/.br_history/issues.20260308_213715_115792063.jsonl.meta.json +0 -1
  282. package/.beads/.br_history/issues.20260308_213715_462220666.jsonl +0 -57
  283. package/.beads/.br_history/issues.20260308_213715_462220666.jsonl.meta.json +0 -1
  284. package/.beads/.br_history/issues.20260308_213727_191258923.jsonl +0 -57
  285. package/.beads/.br_history/issues.20260308_213727_191258923.jsonl.meta.json +0 -1
  286. package/.beads/.br_history/issues.20260308_213727_684383652.jsonl +0 -57
  287. package/.beads/.br_history/issues.20260308_213727_684383652.jsonl.meta.json +0 -1
  288. package/.beads/.br_history/issues.20260308_213735_751882991.jsonl +0 -57
  289. package/.beads/.br_history/issues.20260308_213735_751882991.jsonl.meta.json +0 -1
  290. package/.beads/.br_history/issues.20260308_222052_279844960.jsonl +0 -57
  291. package/.beads/.br_history/issues.20260308_222052_279844960.jsonl.meta.json +0 -1
  292. package/.beads/.br_history/issues.20260308_222056_873282114.jsonl +0 -57
  293. package/.beads/.br_history/issues.20260308_222056_873282114.jsonl.meta.json +0 -1
  294. package/.beads/.br_history/issues.20260308_222103_402410761.jsonl +0 -57
  295. package/.beads/.br_history/issues.20260308_222103_402410761.jsonl.meta.json +0 -1
  296. package/.beads/.br_history/issues.20260308_235202_180577215.jsonl +0 -57
  297. package/.beads/.br_history/issues.20260308_235202_180577215.jsonl.meta.json +0 -1
  298. package/.beads/.br_history/issues.20260308_235202_387414163.jsonl +0 -57
  299. package/.beads/.br_history/issues.20260308_235202_387414163.jsonl.meta.json +0 -1
  300. package/.beads/.br_history/issues.20260308_235202_564422794.jsonl +0 -57
  301. package/.beads/.br_history/issues.20260308_235202_564422794.jsonl.meta.json +0 -1
  302. package/.beads/.br_history/issues.20260308_235202_742600597.jsonl +0 -57
  303. package/.beads/.br_history/issues.20260308_235202_742600597.jsonl.meta.json +0 -1
  304. package/.beads/.br_history/issues.20260308_235208_133360069.jsonl +0 -57
  305. package/.beads/.br_history/issues.20260308_235208_133360069.jsonl.meta.json +0 -1
  306. package/.beads/.br_history/issues.20260308_235505_473406307.jsonl +0 -57
  307. package/.beads/.br_history/issues.20260308_235505_473406307.jsonl.meta.json +0 -1
  308. package/.beads/.br_history/issues.20260308_235505_662360489.jsonl +0 -57
  309. package/.beads/.br_history/issues.20260308_235505_662360489.jsonl.meta.json +0 -1
  310. package/.beads/.br_history/issues.20260308_235505_843935624.jsonl +0 -57
  311. package/.beads/.br_history/issues.20260308_235505_843935624.jsonl.meta.json +0 -1
  312. package/.beads/.br_history/issues.20260308_235506_044530221.jsonl +0 -57
  313. package/.beads/.br_history/issues.20260308_235506_044530221.jsonl.meta.json +0 -1
  314. package/.beads/.br_history/issues.20260309_002618_115728731.jsonl +0 -57
  315. package/.beads/.br_history/issues.20260309_002618_115728731.jsonl.meta.json +0 -1
  316. package/.beads/.br_history/issues.20260309_003748_878174586.jsonl +0 -57
  317. package/.beads/.br_history/issues.20260309_003748_878174586.jsonl.meta.json +0 -1
  318. package/.beads/.br_history/issues.20260309_004057_868755623.jsonl +0 -57
  319. package/.beads/.br_history/issues.20260309_004057_868755623.jsonl.meta.json +0 -1
  320. package/.beads/.br_history/issues.20260309_004058_512842163.jsonl +0 -57
  321. package/.beads/.br_history/issues.20260309_004058_512842163.jsonl.meta.json +0 -1
  322. package/.beads/.br_history/issues.20260309_004058_994445226.jsonl +0 -57
  323. package/.beads/.br_history/issues.20260309_004058_994445226.jsonl.meta.json +0 -1
  324. package/.beads/.br_history/issues.20260309_004059_475988596.jsonl +0 -57
  325. package/.beads/.br_history/issues.20260309_004059_475988596.jsonl.meta.json +0 -1
  326. package/.beads/.br_history/issues.20260309_161902_566857851.jsonl +0 -57
  327. package/.beads/.br_history/issues.20260309_161902_566857851.jsonl.meta.json +0 -1
  328. package/.beads/.br_history/issues.20260309_170512_277017739.jsonl +0 -57
  329. package/.beads/.br_history/issues.20260309_170512_277017739.jsonl.meta.json +0 -1
  330. package/.beads/.br_history/issues.20260309_170512_477876921.jsonl +0 -57
  331. package/.beads/.br_history/issues.20260309_170512_477876921.jsonl.meta.json +0 -1
  332. package/.beads/.br_history/issues.20260309_170512_664382701.jsonl +0 -57
  333. package/.beads/.br_history/issues.20260309_170512_664382701.jsonl.meta.json +0 -1
  334. package/.beads/.br_history/issues.20260309_170512_859400333.jsonl +0 -57
  335. package/.beads/.br_history/issues.20260309_170512_859400333.jsonl.meta.json +0 -1
  336. package/.beads/.br_history/issues.20260309_212326_082771164.jsonl +0 -57
  337. package/.beads/.br_history/issues.20260309_212326_082771164.jsonl.meta.json +0 -1
  338. package/.beads/.br_history/issues.20260309_212326_245619716.jsonl +0 -58
  339. package/.beads/.br_history/issues.20260309_212326_245619716.jsonl.meta.json +0 -1
  340. package/.beads/.br_history/issues.20260309_212326_403198317.jsonl +0 -59
  341. package/.beads/.br_history/issues.20260309_212326_403198317.jsonl.meta.json +0 -1
  342. package/.beads/.br_history/issues.20260309_212332_539197678.jsonl +0 -60
  343. package/.beads/.br_history/issues.20260309_212332_539197678.jsonl.meta.json +0 -1
  344. package/.beads/.br_history/issues.20260309_212332_731373599.jsonl +0 -60
  345. package/.beads/.br_history/issues.20260309_212332_731373599.jsonl.meta.json +0 -1
  346. package/.beads/.br_history/issues.20260309_212332_928710953.jsonl +0 -60
  347. package/.beads/.br_history/issues.20260309_212332_928710953.jsonl.meta.json +0 -1
  348. package/.beads/.br_history/issues.20260309_213021_341505240.jsonl +0 -60
  349. package/.beads/.br_history/issues.20260309_213021_341505240.jsonl.meta.json +0 -1
  350. package/.beads/.br_history/issues.20260309_213022_023136934.jsonl +0 -60
  351. package/.beads/.br_history/issues.20260309_213022_023136934.jsonl.meta.json +0 -1
  352. package/.beads/.br_history/issues.20260309_213022_400050719.jsonl +0 -60
  353. package/.beads/.br_history/issues.20260309_213022_400050719.jsonl.meta.json +0 -1
  354. package/.beads/config.yaml +0 -4
  355. package/.beads/issues.jsonl +0 -60
  356. package/.beads/metadata.json +0 -4
  357. package/docs/mcp-cheatsheet.md +0 -324
  358. package/docs/visual-overview.md +0 -21
  359. package/ref-monty/.cargo/config.toml +0 -3
  360. package/ref-monty/.claude/settings.json +0 -60
  361. package/ref-monty/.claude/skills/fastmod/SKILL.md +0 -22
  362. package/ref-monty/.claude/skills/python-playground/SKILL.md +0 -47
  363. package/ref-monty/.codecov.yml +0 -12
  364. package/ref-monty/.github/actions/build-pgo-wheel/action.yml +0 -72
  365. package/ref-monty/.github/workflows/ci.yml +0 -776
  366. package/ref-monty/.github/workflows/codspeed.yml +0 -45
  367. package/ref-monty/.github/workflows/init-npm-packages.yml +0 -82
  368. package/ref-monty/.pre-commit-config.yaml +0 -47
  369. package/ref-monty/.python-version +0 -1
  370. package/ref-monty/.rustfmt.toml +0 -4
  371. package/ref-monty/.zed/settings.json +0 -11
  372. package/ref-monty/CLAUDE.md +0 -535
  373. package/ref-monty/Cargo.lock +0 -3798
  374. package/ref-monty/Cargo.toml +0 -87
  375. package/ref-monty/LICENSE +0 -21
  376. package/ref-monty/Makefile +0 -216
  377. package/ref-monty/README.md +0 -430
  378. package/ref-monty/RELEASING.md +0 -47
  379. package/ref-monty/crates/fuzz/Cargo.toml +0 -30
  380. package/ref-monty/crates/fuzz/fuzz_targets/string_input_panic.rs +0 -37
  381. package/ref-monty/crates/fuzz/fuzz_targets/tokens_input_panic.rs +0 -552
  382. package/ref-monty/crates/monty/Cargo.toml +0 -68
  383. package/ref-monty/crates/monty/benches/main.rs +0 -247
  384. package/ref-monty/crates/monty/build.rs +0 -10
  385. package/ref-monty/crates/monty/src/args.rs +0 -733
  386. package/ref-monty/crates/monty/src/asyncio.rs +0 -179
  387. package/ref-monty/crates/monty/src/builtins/abs.rs +0 -55
  388. package/ref-monty/crates/monty/src/builtins/all.rs +0 -30
  389. package/ref-monty/crates/monty/src/builtins/any.rs +0 -30
  390. package/ref-monty/crates/monty/src/builtins/bin.rs +0 -59
  391. package/ref-monty/crates/monty/src/builtins/chr.rs +0 -46
  392. package/ref-monty/crates/monty/src/builtins/divmod.rs +0 -164
  393. package/ref-monty/crates/monty/src/builtins/enumerate.rs +0 -52
  394. package/ref-monty/crates/monty/src/builtins/filter.rs +0 -67
  395. package/ref-monty/crates/monty/src/builtins/getattr.rs +0 -65
  396. package/ref-monty/crates/monty/src/builtins/hash.rs +0 -28
  397. package/ref-monty/crates/monty/src/builtins/hex.rs +0 -58
  398. package/ref-monty/crates/monty/src/builtins/id.rs +0 -24
  399. package/ref-monty/crates/monty/src/builtins/isinstance.rs +0 -68
  400. package/ref-monty/crates/monty/src/builtins/len.rs +0 -25
  401. package/ref-monty/crates/monty/src/builtins/map.rs +0 -98
  402. package/ref-monty/crates/monty/src/builtins/min_max.rs +0 -113
  403. package/ref-monty/crates/monty/src/builtins/mod.rs +0 -246
  404. package/ref-monty/crates/monty/src/builtins/next.rs +0 -21
  405. package/ref-monty/crates/monty/src/builtins/oct.rs +0 -59
  406. package/ref-monty/crates/monty/src/builtins/ord.rs +0 -67
  407. package/ref-monty/crates/monty/src/builtins/pow.rs +0 -365
  408. package/ref-monty/crates/monty/src/builtins/print.rs +0 -141
  409. package/ref-monty/crates/monty/src/builtins/repr.rs +0 -16
  410. package/ref-monty/crates/monty/src/builtins/reversed.rs +0 -28
  411. package/ref-monty/crates/monty/src/builtins/round.rs +0 -174
  412. package/ref-monty/crates/monty/src/builtins/sorted.rs +0 -151
  413. package/ref-monty/crates/monty/src/builtins/sum.rs +0 -66
  414. package/ref-monty/crates/monty/src/builtins/type_.rs +0 -16
  415. package/ref-monty/crates/monty/src/builtins/zip.rs +0 -77
  416. package/ref-monty/crates/monty/src/bytecode/builder.rs +0 -699
  417. package/ref-monty/crates/monty/src/bytecode/code.rs +0 -310
  418. package/ref-monty/crates/monty/src/bytecode/compiler.rs +0 -3206
  419. package/ref-monty/crates/monty/src/bytecode/mod.rs +0 -24
  420. package/ref-monty/crates/monty/src/bytecode/op.rs +0 -617
  421. package/ref-monty/crates/monty/src/bytecode/vm/async_exec.rs +0 -1058
  422. package/ref-monty/crates/monty/src/bytecode/vm/attr.rs +0 -63
  423. package/ref-monty/crates/monty/src/bytecode/vm/binary.rs +0 -487
  424. package/ref-monty/crates/monty/src/bytecode/vm/call.rs +0 -767
  425. package/ref-monty/crates/monty/src/bytecode/vm/collections.rs +0 -741
  426. package/ref-monty/crates/monty/src/bytecode/vm/compare.rs +0 -147
  427. package/ref-monty/crates/monty/src/bytecode/vm/exceptions.rs +0 -297
  428. package/ref-monty/crates/monty/src/bytecode/vm/format.rs +0 -132
  429. package/ref-monty/crates/monty/src/bytecode/vm/mod.rs +0 -1958
  430. package/ref-monty/crates/monty/src/bytecode/vm/scheduler.rs +0 -620
  431. package/ref-monty/crates/monty/src/exception_private.rs +0 -1513
  432. package/ref-monty/crates/monty/src/exception_public.rs +0 -346
  433. package/ref-monty/crates/monty/src/expressions.rs +0 -694
  434. package/ref-monty/crates/monty/src/fstring.rs +0 -854
  435. package/ref-monty/crates/monty/src/function.rs +0 -119
  436. package/ref-monty/crates/monty/src/heap.rs +0 -1073
  437. package/ref-monty/crates/monty/src/heap_data.rs +0 -985
  438. package/ref-monty/crates/monty/src/heap_traits.rs +0 -312
  439. package/ref-monty/crates/monty/src/intern.rs +0 -837
  440. package/ref-monty/crates/monty/src/io.rs +0 -106
  441. package/ref-monty/crates/monty/src/lib.rs +0 -52
  442. package/ref-monty/crates/monty/src/modules/asyncio.rs +0 -144
  443. package/ref-monty/crates/monty/src/modules/math.rs +0 -1453
  444. package/ref-monty/crates/monty/src/modules/mod.rs +0 -120
  445. package/ref-monty/crates/monty/src/modules/os.rs +0 -116
  446. package/ref-monty/crates/monty/src/modules/pathlib.rs +0 -33
  447. package/ref-monty/crates/monty/src/modules/re.rs +0 -606
  448. package/ref-monty/crates/monty/src/modules/sys.rs +0 -60
  449. package/ref-monty/crates/monty/src/modules/typing.rs +0 -70
  450. package/ref-monty/crates/monty/src/namespace.rs +0 -21
  451. package/ref-monty/crates/monty/src/object.rs +0 -1040
  452. package/ref-monty/crates/monty/src/os.rs +0 -215
  453. package/ref-monty/crates/monty/src/parse.rs +0 -1730
  454. package/ref-monty/crates/monty/src/prepare.rs +0 -3015
  455. package/ref-monty/crates/monty/src/repl.rs +0 -1109
  456. package/ref-monty/crates/monty/src/resource.rs +0 -559
  457. package/ref-monty/crates/monty/src/run.rs +0 -457
  458. package/ref-monty/crates/monty/src/run_progress.rs +0 -821
  459. package/ref-monty/crates/monty/src/signature.rs +0 -651
  460. package/ref-monty/crates/monty/src/sorting.rs +0 -100
  461. package/ref-monty/crates/monty/src/types/bytes.rs +0 -2356
  462. package/ref-monty/crates/monty/src/types/dataclass.rs +0 -345
  463. package/ref-monty/crates/monty/src/types/dict.rs +0 -879
  464. package/ref-monty/crates/monty/src/types/dict_view.rs +0 -619
  465. package/ref-monty/crates/monty/src/types/iter.rs +0 -799
  466. package/ref-monty/crates/monty/src/types/list.rs +0 -929
  467. package/ref-monty/crates/monty/src/types/long_int.rs +0 -211
  468. package/ref-monty/crates/monty/src/types/mod.rs +0 -48
  469. package/ref-monty/crates/monty/src/types/module.rs +0 -146
  470. package/ref-monty/crates/monty/src/types/namedtuple.rs +0 -261
  471. package/ref-monty/crates/monty/src/types/path.rs +0 -596
  472. package/ref-monty/crates/monty/src/types/property.rs +0 -35
  473. package/ref-monty/crates/monty/src/types/py_trait.rs +0 -322
  474. package/ref-monty/crates/monty/src/types/range.rs +0 -285
  475. package/ref-monty/crates/monty/src/types/re_match.rs +0 -522
  476. package/ref-monty/crates/monty/src/types/re_pattern.rs +0 -726
  477. package/ref-monty/crates/monty/src/types/set.rs +0 -1373
  478. package/ref-monty/crates/monty/src/types/slice.rs +0 -257
  479. package/ref-monty/crates/monty/src/types/str.rs +0 -2051
  480. package/ref-monty/crates/monty/src/types/tuple.rs +0 -376
  481. package/ref-monty/crates/monty/src/types/type.rs +0 -407
  482. package/ref-monty/crates/monty/src/value.rs +0 -2558
  483. package/ref-monty/crates/monty/test_cases/args__dict_get_no_args.py +0 -3
  484. package/ref-monty/crates/monty/test_cases/args__dict_get_too_many.py +0 -3
  485. package/ref-monty/crates/monty/test_cases/args__dict_items_with_args.py +0 -3
  486. package/ref-monty/crates/monty/test_cases/args__dict_keys_with_args.py +0 -3
  487. package/ref-monty/crates/monty/test_cases/args__dict_pop_no_args.py +0 -3
  488. package/ref-monty/crates/monty/test_cases/args__dict_pop_too_many.py +0 -3
  489. package/ref-monty/crates/monty/test_cases/args__dict_values_with_args.py +0 -3
  490. package/ref-monty/crates/monty/test_cases/args__id_too_many.py +0 -2
  491. package/ref-monty/crates/monty/test_cases/args__len_no_args.py +0 -2
  492. package/ref-monty/crates/monty/test_cases/args__len_too_many.py +0 -2
  493. package/ref-monty/crates/monty/test_cases/args__len_type_error_int.py +0 -9
  494. package/ref-monty/crates/monty/test_cases/args__len_type_error_none.py +0 -9
  495. package/ref-monty/crates/monty/test_cases/args__list_append_no_args.py +0 -3
  496. package/ref-monty/crates/monty/test_cases/args__list_append_too_many.py +0 -3
  497. package/ref-monty/crates/monty/test_cases/args__list_insert_too_few.py +0 -3
  498. package/ref-monty/crates/monty/test_cases/args__list_insert_too_many.py +0 -3
  499. package/ref-monty/crates/monty/test_cases/args__repr_no_args.py +0 -2
  500. package/ref-monty/crates/monty/test_cases/arith__div_zero_float.py +0 -2
  501. package/ref-monty/crates/monty/test_cases/arith__div_zero_int.py +0 -2
  502. package/ref-monty/crates/monty/test_cases/arith__floordiv_zero_float.py +0 -2
  503. package/ref-monty/crates/monty/test_cases/arith__floordiv_zero_int.py +0 -2
  504. package/ref-monty/crates/monty/test_cases/arith__pow_zero_neg.py +0 -2
  505. package/ref-monty/crates/monty/test_cases/arith__pow_zero_neg_builtin.py +0 -9
  506. package/ref-monty/crates/monty/test_cases/assert__expr_fail.py +0 -2
  507. package/ref-monty/crates/monty/test_cases/assert__fail.py +0 -2
  508. package/ref-monty/crates/monty/test_cases/assert__fail_msg.py +0 -2
  509. package/ref-monty/crates/monty/test_cases/assert__fn_fail.py +0 -3
  510. package/ref-monty/crates/monty/test_cases/assert__ops.py +0 -11
  511. package/ref-monty/crates/monty/test_cases/async__asyncio_run.py +0 -47
  512. package/ref-monty/crates/monty/test_cases/async__basic.py +0 -10
  513. package/ref-monty/crates/monty/test_cases/async__closure.py +0 -14
  514. package/ref-monty/crates/monty/test_cases/async__double_await_coroutine.py +0 -16
  515. package/ref-monty/crates/monty/test_cases/async__exception.py +0 -10
  516. package/ref-monty/crates/monty/test_cases/async__ext_call.py +0 -73
  517. package/ref-monty/crates/monty/test_cases/async__gather_all.py +0 -85
  518. package/ref-monty/crates/monty/test_cases/async__nested_await.py +0 -15
  519. package/ref-monty/crates/monty/test_cases/async__nested_gather_ext.py +0 -37
  520. package/ref-monty/crates/monty/test_cases/async__not_awaitable.py +0 -10
  521. package/ref-monty/crates/monty/test_cases/async__not_imported.py +0 -14
  522. package/ref-monty/crates/monty/test_cases/async__recursion_depth_isolation.py +0 -27
  523. package/ref-monty/crates/monty/test_cases/async__return_types.py +0 -31
  524. package/ref-monty/crates/monty/test_cases/async__sequential.py +0 -16
  525. package/ref-monty/crates/monty/test_cases/async__traceback.py +0 -19
  526. package/ref-monty/crates/monty/test_cases/async__with_args.py +0 -14
  527. package/ref-monty/crates/monty/test_cases/attr__get_int_error.py +0 -9
  528. package/ref-monty/crates/monty/test_cases/attr__get_list_error.py +0 -9
  529. package/ref-monty/crates/monty/test_cases/attr__set_frozen_nonfield.py +0 -12
  530. package/ref-monty/crates/monty/test_cases/attr__set_int_error.py +0 -10
  531. package/ref-monty/crates/monty/test_cases/attr__set_list_error.py +0 -10
  532. package/ref-monty/crates/monty/test_cases/bench__kitchen_sink.py +0 -68
  533. package/ref-monty/crates/monty/test_cases/bool__ops.py +0 -20
  534. package/ref-monty/crates/monty/test_cases/builtin__add_type_error.py +0 -2
  535. package/ref-monty/crates/monty/test_cases/builtin__filter.py +0 -62
  536. package/ref-monty/crates/monty/test_cases/builtin__filter_not_iterable.py +0 -11
  537. package/ref-monty/crates/monty/test_cases/builtin__getattr.py +0 -84
  538. package/ref-monty/crates/monty/test_cases/builtin__iter_funcs.py +0 -42
  539. package/ref-monty/crates/monty/test_cases/builtin__iter_next.py +0 -66
  540. package/ref-monty/crates/monty/test_cases/builtin__map.py +0 -74
  541. package/ref-monty/crates/monty/test_cases/builtin__map_not_iterable.py +0 -11
  542. package/ref-monty/crates/monty/test_cases/builtin__math_funcs.py +0 -154
  543. package/ref-monty/crates/monty/test_cases/builtin__more_iter_funcs.py +0 -148
  544. package/ref-monty/crates/monty/test_cases/builtin__next_stop_iteration.py +0 -10
  545. package/ref-monty/crates/monty/test_cases/builtin__print_invalid_kwarg.py +0 -9
  546. package/ref-monty/crates/monty/test_cases/builtin__print_kwargs.py +0 -12
  547. package/ref-monty/crates/monty/test_cases/builtin__repr.py +0 -3
  548. package/ref-monty/crates/monty/test_cases/builtin__string_funcs.py +0 -73
  549. package/ref-monty/crates/monty/test_cases/bytes__decode_invalid_utf8.py +0 -18
  550. package/ref-monty/crates/monty/test_cases/bytes__endswith_str_error.py +0 -10
  551. package/ref-monty/crates/monty/test_cases/bytes__getitem_index_error.py +0 -10
  552. package/ref-monty/crates/monty/test_cases/bytes__index_start_gt_end.py +0 -10
  553. package/ref-monty/crates/monty/test_cases/bytes__methods.py +0 -394
  554. package/ref-monty/crates/monty/test_cases/bytes__negative_count.py +0 -9
  555. package/ref-monty/crates/monty/test_cases/bytes__ops.py +0 -90
  556. package/ref-monty/crates/monty/test_cases/bytes__startswith_str_error.py +0 -10
  557. package/ref-monty/crates/monty/test_cases/call_object.py +0 -3
  558. package/ref-monty/crates/monty/test_cases/chain_comparison__all.py +0 -79
  559. package/ref-monty/crates/monty/test_cases/closure__param_shadows_outer.py +0 -81
  560. package/ref-monty/crates/monty/test_cases/closure__pep448.py +0 -203
  561. package/ref-monty/crates/monty/test_cases/closure__undefined_nonlocal.py +0 -13
  562. package/ref-monty/crates/monty/test_cases/compare__mixed_types.py +0 -120
  563. package/ref-monty/crates/monty/test_cases/comprehension__all.py +0 -208
  564. package/ref-monty/crates/monty/test_cases/comprehension__scope.py +0 -7
  565. package/ref-monty/crates/monty/test_cases/comprehension__unbound_local.py +0 -14
  566. package/ref-monty/crates/monty/test_cases/dataclass__basic.py +0 -238
  567. package/ref-monty/crates/monty/test_cases/dataclass__call_field_error.py +0 -12
  568. package/ref-monty/crates/monty/test_cases/dataclass__frozen_set_error.py +0 -12
  569. package/ref-monty/crates/monty/test_cases/dataclass__get_missing_attr_error.py +0 -11
  570. package/ref-monty/crates/monty/test_cases/dict__get_unhashable_key.py +0 -3
  571. package/ref-monty/crates/monty/test_cases/dict__literal_unhashable_key.py +0 -2
  572. package/ref-monty/crates/monty/test_cases/dict__method_pop_missing_error.py +0 -3
  573. package/ref-monty/crates/monty/test_cases/dict__methods.py +0 -151
  574. package/ref-monty/crates/monty/test_cases/dict__ops.py +0 -133
  575. package/ref-monty/crates/monty/test_cases/dict__pop_unhashable_key.py +0 -4
  576. package/ref-monty/crates/monty/test_cases/dict__popitem_empty.py +0 -9
  577. package/ref-monty/crates/monty/test_cases/dict__subscript_missing_key.py +0 -3
  578. package/ref-monty/crates/monty/test_cases/dict__unhashable_dict_key.py +0 -2
  579. package/ref-monty/crates/monty/test_cases/dict__unhashable_list_key.py +0 -2
  580. package/ref-monty/crates/monty/test_cases/dict__unpack_type_error.py +0 -2
  581. package/ref-monty/crates/monty/test_cases/dict__views.py +0 -165
  582. package/ref-monty/crates/monty/test_cases/edge__all.py +0 -26
  583. package/ref-monty/crates/monty/test_cases/edge__float_int_mod.py +0 -2
  584. package/ref-monty/crates/monty/test_cases/edge__int_float_mod.py +0 -2
  585. package/ref-monty/crates/monty/test_cases/exc__args.py +0 -16
  586. package/ref-monty/crates/monty/test_cases/exc__str.py +0 -15
  587. package/ref-monty/crates/monty/test_cases/execute_ok__all.py +0 -54
  588. package/ref-monty/crates/monty/test_cases/execute_raise__error_instance_str.py +0 -2
  589. package/ref-monty/crates/monty/test_cases/execute_raise__error_no_args.py +0 -2
  590. package/ref-monty/crates/monty/test_cases/execute_raise__error_string_arg.py +0 -2
  591. package/ref-monty/crates/monty/test_cases/execute_raise__error_string_arg_quotes.py +0 -2
  592. package/ref-monty/crates/monty/test_cases/execute_raise__error_type.py +0 -2
  593. package/ref-monty/crates/monty/test_cases/execute_raise__raise_instance_via_var.py +0 -4
  594. package/ref-monty/crates/monty/test_cases/execute_raise__raise_list.py +0 -2
  595. package/ref-monty/crates/monty/test_cases/execute_raise__raise_number.py +0 -2
  596. package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_call_via_var.py +0 -4
  597. package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_direct.py +0 -3
  598. package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_via_var.py +0 -4
  599. package/ref-monty/crates/monty/test_cases/ext_call__arg_side_effect_bug.py +0 -22
  600. package/ref-monty/crates/monty/test_cases/ext_call__augmented.py +0 -17
  601. package/ref-monty/crates/monty/test_cases/ext_call__augmented_refcount_bug.py +0 -7
  602. package/ref-monty/crates/monty/test_cases/ext_call__bare_raise_after_resume.py +0 -34
  603. package/ref-monty/crates/monty/test_cases/ext_call__basic.py +0 -99
  604. package/ref-monty/crates/monty/test_cases/ext_call__boolean.py +0 -37
  605. package/ref-monty/crates/monty/test_cases/ext_call__boolean_side_effect_hang.py +0 -17
  606. package/ref-monty/crates/monty/test_cases/ext_call__closure_bug.py +0 -16
  607. package/ref-monty/crates/monty/test_cases/ext_call__comparison.py +0 -26
  608. package/ref-monty/crates/monty/test_cases/ext_call__deep_call_stack.py +0 -18
  609. package/ref-monty/crates/monty/test_cases/ext_call__elif.py +0 -171
  610. package/ref-monty/crates/monty/test_cases/ext_call__exc.py +0 -4
  611. package/ref-monty/crates/monty/test_cases/ext_call__exc_deep_stack.py +0 -39
  612. package/ref-monty/crates/monty/test_cases/ext_call__exc_in_function.py +0 -17
  613. package/ref-monty/crates/monty/test_cases/ext_call__exc_nested_functions.py +0 -31
  614. package/ref-monty/crates/monty/test_cases/ext_call__ext_exc.py +0 -171
  615. package/ref-monty/crates/monty/test_cases/ext_call__for.py +0 -114
  616. package/ref-monty/crates/monty/test_cases/ext_call__fstring.py +0 -12
  617. package/ref-monty/crates/monty/test_cases/ext_call__if.py +0 -135
  618. package/ref-monty/crates/monty/test_cases/ext_call__if_condition.py +0 -37
  619. package/ref-monty/crates/monty/test_cases/ext_call__in_closure.py +0 -14
  620. package/ref-monty/crates/monty/test_cases/ext_call__in_function.py +0 -40
  621. package/ref-monty/crates/monty/test_cases/ext_call__in_function_simple.py +0 -7
  622. package/ref-monty/crates/monty/test_cases/ext_call__literals.py +0 -17
  623. package/ref-monty/crates/monty/test_cases/ext_call__multi_in_func.py +0 -32
  624. package/ref-monty/crates/monty/test_cases/ext_call__name_lookup.py +0 -69
  625. package/ref-monty/crates/monty/test_cases/ext_call__name_lookup_undefined.py +0 -4
  626. package/ref-monty/crates/monty/test_cases/ext_call__nested_calls.py +0 -14
  627. package/ref-monty/crates/monty/test_cases/ext_call__recursion_bug.py +0 -19
  628. package/ref-monty/crates/monty/test_cases/ext_call__return.py +0 -28
  629. package/ref-monty/crates/monty/test_cases/ext_call__side_effects.py +0 -25
  630. package/ref-monty/crates/monty/test_cases/ext_call__subscript.py +0 -7
  631. package/ref-monty/crates/monty/test_cases/ext_call__ternary.py +0 -28
  632. package/ref-monty/crates/monty/test_cases/ext_call__try.py +0 -280
  633. package/ref-monty/crates/monty/test_cases/ext_call__try_simple.py +0 -10
  634. package/ref-monty/crates/monty/test_cases/ext_call__unary.py +0 -13
  635. package/ref-monty/crates/monty/test_cases/frozenset__ops.py +0 -178
  636. package/ref-monty/crates/monty/test_cases/fstring__all.py +0 -236
  637. package/ref-monty/crates/monty/test_cases/fstring__error_eq_align_on_str.py +0 -3
  638. package/ref-monty/crates/monty/test_cases/fstring__error_float_f_on_str.py +0 -3
  639. package/ref-monty/crates/monty/test_cases/fstring__error_int_d_on_float.py +0 -3
  640. package/ref-monty/crates/monty/test_cases/fstring__error_int_d_on_str.py +0 -3
  641. package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec.py +0 -4
  642. package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec_dynamic.py +0 -4
  643. package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec_str.py +0 -4
  644. package/ref-monty/crates/monty/test_cases/fstring__error_str_s_on_int.py +0 -3
  645. package/ref-monty/crates/monty/test_cases/function__call_duplicate_kwargs.py +0 -6
  646. package/ref-monty/crates/monty/test_cases/function__call_unpack.py +0 -42
  647. package/ref-monty/crates/monty/test_cases/function__defaults.py +0 -117
  648. package/ref-monty/crates/monty/test_cases/function__err_duplicate_arg.py +0 -7
  649. package/ref-monty/crates/monty/test_cases/function__err_duplicate_first_arg.py +0 -7
  650. package/ref-monty/crates/monty/test_cases/function__err_duplicate_kwarg_cleanup.py +0 -9
  651. package/ref-monty/crates/monty/test_cases/function__err_kwonly_as_positional.py +0 -7
  652. package/ref-monty/crates/monty/test_cases/function__err_missing_all_posonly.py +0 -7
  653. package/ref-monty/crates/monty/test_cases/function__err_missing_heap_cleanup.py +0 -9
  654. package/ref-monty/crates/monty/test_cases/function__err_missing_kwonly.py +0 -7
  655. package/ref-monty/crates/monty/test_cases/function__err_missing_posonly_with_kwarg.py +0 -7
  656. package/ref-monty/crates/monty/test_cases/function__err_missing_with_posonly.py +0 -7
  657. package/ref-monty/crates/monty/test_cases/function__err_posonly_as_kwarg.py +0 -7
  658. package/ref-monty/crates/monty/test_cases/function__err_posonly_first_as_kwarg.py +0 -7
  659. package/ref-monty/crates/monty/test_cases/function__err_too_many_posonly.py +0 -7
  660. package/ref-monty/crates/monty/test_cases/function__err_too_many_with_kwonly.py +0 -7
  661. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg.py +0 -7
  662. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_cleanup.py +0 -9
  663. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_quote.py +0 -13
  664. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_simple.py +0 -7
  665. package/ref-monty/crates/monty/test_cases/function__err_unpack_duplicate_arg.py +0 -6
  666. package/ref-monty/crates/monty/test_cases/function__err_unpack_duplicate_heap.py +0 -8
  667. package/ref-monty/crates/monty/test_cases/function__err_unpack_int.py +0 -6
  668. package/ref-monty/crates/monty/test_cases/function__err_unpack_nonstring_key.py +0 -6
  669. package/ref-monty/crates/monty/test_cases/function__err_unpack_not_mapping.py +0 -6
  670. package/ref-monty/crates/monty/test_cases/function__kwargs_unpacking.py +0 -173
  671. package/ref-monty/crates/monty/test_cases/function__ops.py +0 -294
  672. package/ref-monty/crates/monty/test_cases/function__return_none.py +0 -42
  673. package/ref-monty/crates/monty/test_cases/function__signatures.py +0 -47
  674. package/ref-monty/crates/monty/test_cases/function__too_few_args_all.py +0 -6
  675. package/ref-monty/crates/monty/test_cases/function__too_few_args_one.py +0 -6
  676. package/ref-monty/crates/monty/test_cases/function__too_few_args_two.py +0 -6
  677. package/ref-monty/crates/monty/test_cases/function__too_many_args_one.py +0 -6
  678. package/ref-monty/crates/monty/test_cases/function__too_many_args_two.py +0 -6
  679. package/ref-monty/crates/monty/test_cases/function__too_many_args_zero.py +0 -6
  680. package/ref-monty/crates/monty/test_cases/global__error_assigned_before.py +0 -7
  681. package/ref-monty/crates/monty/test_cases/global__ops.py +0 -163
  682. package/ref-monty/crates/monty/test_cases/hash__dict_unhashable.py +0 -2
  683. package/ref-monty/crates/monty/test_cases/hash__list_unhashable.py +0 -2
  684. package/ref-monty/crates/monty/test_cases/hash__ops.py +0 -153
  685. package/ref-monty/crates/monty/test_cases/id__bytes_literals_distinct.py +0 -3
  686. package/ref-monty/crates/monty/test_cases/id__int_copy_distinct.py +0 -5
  687. package/ref-monty/crates/monty/test_cases/id__is_number_is_number.py +0 -3
  688. package/ref-monty/crates/monty/test_cases/id__non_overlapping_lifetimes_distinct_types.py +0 -10
  689. package/ref-monty/crates/monty/test_cases/id__non_overlapping_lifetimes_same_types.py +0 -6
  690. package/ref-monty/crates/monty/test_cases/id__ops.py +0 -97
  691. package/ref-monty/crates/monty/test_cases/id__str_literals_same.py +0 -3
  692. package/ref-monty/crates/monty/test_cases/if__elif_else.py +0 -207
  693. package/ref-monty/crates/monty/test_cases/if__raise_elif.py +0 -11
  694. package/ref-monty/crates/monty/test_cases/if__raise_else.py +0 -13
  695. package/ref-monty/crates/monty/test_cases/if__raise_if.py +0 -9
  696. package/ref-monty/crates/monty/test_cases/if__raise_in_elif_condition.py +0 -18
  697. package/ref-monty/crates/monty/test_cases/if__raise_in_if_condition.py +0 -16
  698. package/ref-monty/crates/monty/test_cases/if_else_expr__all.py +0 -55
  699. package/ref-monty/crates/monty/test_cases/import__error_cannot_import.py +0 -9
  700. package/ref-monty/crates/monty/test_cases/import__error_module_not_found.py +0 -9
  701. package/ref-monty/crates/monty/test_cases/import__local_scope.py +0 -68
  702. package/ref-monty/crates/monty/test_cases/import__os.py +0 -25
  703. package/ref-monty/crates/monty/test_cases/import__relative_error.py +0 -9
  704. package/ref-monty/crates/monty/test_cases/import__relative_no_module_error.py +0 -9
  705. package/ref-monty/crates/monty/test_cases/import__runtime_error_when_executed.py +0 -14
  706. package/ref-monty/crates/monty/test_cases/import__star_error.py +0 -11
  707. package/ref-monty/crates/monty/test_cases/import__sys.py +0 -47
  708. package/ref-monty/crates/monty/test_cases/import__sys_monty.py +0 -28
  709. package/ref-monty/crates/monty/test_cases/import__type_checking_guard.py +0 -37
  710. package/ref-monty/crates/monty/test_cases/import__typing.py +0 -25
  711. package/ref-monty/crates/monty/test_cases/import__typing_type_ignore.py +0 -4
  712. package/ref-monty/crates/monty/test_cases/int__bigint.py +0 -467
  713. package/ref-monty/crates/monty/test_cases/int__bigint_errors.py +0 -260
  714. package/ref-monty/crates/monty/test_cases/int__ops.py +0 -219
  715. package/ref-monty/crates/monty/test_cases/int__overflow_division.py +0 -84
  716. package/ref-monty/crates/monty/test_cases/is_variant__all.py +0 -36
  717. package/ref-monty/crates/monty/test_cases/isinstance__arg2_list_error.py +0 -2
  718. package/ref-monty/crates/monty/test_cases/isinstance__arg2_type_error.py +0 -2
  719. package/ref-monty/crates/monty/test_cases/iter__dict_mutation.py +0 -4
  720. package/ref-monty/crates/monty/test_cases/iter__for.py +0 -243
  721. package/ref-monty/crates/monty/test_cases/iter__for_loop_unpacking.py +0 -66
  722. package/ref-monty/crates/monty/test_cases/iter__generator_expr.py +0 -20
  723. package/ref-monty/crates/monty/test_cases/iter__generator_expr_type.py +0 -7
  724. package/ref-monty/crates/monty/test_cases/iter__not_iterable.py +0 -3
  725. package/ref-monty/crates/monty/test_cases/lambda__all.py +0 -145
  726. package/ref-monty/crates/monty/test_cases/list__extend_not_iterable.py +0 -7
  727. package/ref-monty/crates/monty/test_cases/list__getitem_out_of_bounds.py +0 -3
  728. package/ref-monty/crates/monty/test_cases/list__index_not_found.py +0 -9
  729. package/ref-monty/crates/monty/test_cases/list__index_start_gt_end.py +0 -10
  730. package/ref-monty/crates/monty/test_cases/list__ops.py +0 -473
  731. package/ref-monty/crates/monty/test_cases/list__pop_empty.py +0 -9
  732. package/ref-monty/crates/monty/test_cases/list__pop_out_of_range.py +0 -9
  733. package/ref-monty/crates/monty/test_cases/list__pop_type_error.py +0 -9
  734. package/ref-monty/crates/monty/test_cases/list__remove_not_found.py +0 -9
  735. package/ref-monty/crates/monty/test_cases/list__setitem_dict_index.py +0 -13
  736. package/ref-monty/crates/monty/test_cases/list__setitem_huge_int_index.py +0 -13
  737. package/ref-monty/crates/monty/test_cases/list__setitem_index_error.py +0 -10
  738. package/ref-monty/crates/monty/test_cases/list__setitem_type_error.py +0 -10
  739. package/ref-monty/crates/monty/test_cases/list__unpack_type_error.py +0 -2
  740. package/ref-monty/crates/monty/test_cases/longint__index_error.py +0 -3
  741. package/ref-monty/crates/monty/test_cases/longint__repeat_error.py +0 -3
  742. package/ref-monty/crates/monty/test_cases/loop__break_continue.py +0 -113
  743. package/ref-monty/crates/monty/test_cases/loop__break_finally.py +0 -69
  744. package/ref-monty/crates/monty/test_cases/loop__break_in_function_error.py +0 -13
  745. package/ref-monty/crates/monty/test_cases/loop__break_in_if_error.py +0 -11
  746. package/ref-monty/crates/monty/test_cases/loop__break_nested_except_clears.py +0 -55
  747. package/ref-monty/crates/monty/test_cases/loop__break_outside_error.py +0 -9
  748. package/ref-monty/crates/monty/test_cases/loop__continue_finally.py +0 -81
  749. package/ref-monty/crates/monty/test_cases/loop__continue_in_function_error.py +0 -13
  750. package/ref-monty/crates/monty/test_cases/loop__continue_in_if_error.py +0 -11
  751. package/ref-monty/crates/monty/test_cases/loop__continue_nested_except_clears.py +0 -60
  752. package/ref-monty/crates/monty/test_cases/loop__continue_outside_error.py +0 -9
  753. package/ref-monty/crates/monty/test_cases/math__acos_domain_error.py +0 -11
  754. package/ref-monty/crates/monty/test_cases/math__acosh_domain_error.py +0 -11
  755. package/ref-monty/crates/monty/test_cases/math__asin_domain_error.py +0 -11
  756. package/ref-monty/crates/monty/test_cases/math__atanh_domain_error.py +0 -11
  757. package/ref-monty/crates/monty/test_cases/math__cos_inf_error.py +0 -11
  758. package/ref-monty/crates/monty/test_cases/math__cosh_overflow_error.py +0 -11
  759. package/ref-monty/crates/monty/test_cases/math__exp_overflow_error.py +0 -11
  760. package/ref-monty/crates/monty/test_cases/math__factorial_float_error.py +0 -11
  761. package/ref-monty/crates/monty/test_cases/math__factorial_negative_error.py +0 -11
  762. package/ref-monty/crates/monty/test_cases/math__floor_inf_error.py +0 -11
  763. package/ref-monty/crates/monty/test_cases/math__floor_nan_error.py +0 -11
  764. package/ref-monty/crates/monty/test_cases/math__floor_str_error.py +0 -11
  765. package/ref-monty/crates/monty/test_cases/math__fmod_inf_error.py +0 -11
  766. package/ref-monty/crates/monty/test_cases/math__gamma_neg_int_error.py +0 -11
  767. package/ref-monty/crates/monty/test_cases/math__gcd_float_error.py +0 -11
  768. package/ref-monty/crates/monty/test_cases/math__isqrt_negative_error.py +0 -11
  769. package/ref-monty/crates/monty/test_cases/math__ldexp_overflow_error.py +0 -11
  770. package/ref-monty/crates/monty/test_cases/math__log1p_domain_error.py +0 -11
  771. package/ref-monty/crates/monty/test_cases/math__log_base1_error.py +0 -11
  772. package/ref-monty/crates/monty/test_cases/math__log_zero_error.py +0 -11
  773. package/ref-monty/crates/monty/test_cases/math__module.py +0 -1432
  774. package/ref-monty/crates/monty/test_cases/math__pow_domain_error.py +0 -11
  775. package/ref-monty/crates/monty/test_cases/math__sin_inf_error.py +0 -11
  776. package/ref-monty/crates/monty/test_cases/math__sqrt_negative_error.py +0 -11
  777. package/ref-monty/crates/monty/test_cases/math__tan_inf_error.py +0 -11
  778. package/ref-monty/crates/monty/test_cases/math__trunc_str_error.py +0 -11
  779. package/ref-monty/crates/monty/test_cases/method__args_kwargs_unpacking.py +0 -259
  780. package/ref-monty/crates/monty/test_cases/name_error__unbound_local_func.py +0 -19
  781. package/ref-monty/crates/monty/test_cases/name_error__unbound_local_module.py +0 -12
  782. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_chained.py +0 -9
  783. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_in_expr.py +0 -9
  784. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_in_function.py +0 -16
  785. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_with_args.py +0 -9
  786. package/ref-monty/crates/monty/test_cases/name_error__undefined_global.py +0 -10
  787. package/ref-monty/crates/monty/test_cases/namedtuple__missing_attr.py +0 -11
  788. package/ref-monty/crates/monty/test_cases/namedtuple__ops.py +0 -34
  789. package/ref-monty/crates/monty/test_cases/nonlocal__error_module_level.py +0 -3
  790. package/ref-monty/crates/monty/test_cases/nonlocal__ops.py +0 -353
  791. package/ref-monty/crates/monty/test_cases/os__environ.py +0 -40
  792. package/ref-monty/crates/monty/test_cases/os__getenv_key_list_error.py +0 -5
  793. package/ref-monty/crates/monty/test_cases/os__getenv_key_type_error.py +0 -5
  794. package/ref-monty/crates/monty/test_cases/parse_error__complex.py +0 -3
  795. package/ref-monty/crates/monty/test_cases/pathlib__import.py +0 -11
  796. package/ref-monty/crates/monty/test_cases/pathlib__os.py +0 -136
  797. package/ref-monty/crates/monty/test_cases/pathlib__os_read_error.py +0 -12
  798. package/ref-monty/crates/monty/test_cases/pathlib__pure.py +0 -81
  799. package/ref-monty/crates/monty/test_cases/pyobject__cycle_dict_self.py +0 -5
  800. package/ref-monty/crates/monty/test_cases/pyobject__cycle_list_dict.py +0 -6
  801. package/ref-monty/crates/monty/test_cases/pyobject__cycle_list_self.py +0 -5
  802. package/ref-monty/crates/monty/test_cases/pyobject__cycle_multiple_refs.py +0 -6
  803. package/ref-monty/crates/monty/test_cases/range__error_no_args.py +0 -2
  804. package/ref-monty/crates/monty/test_cases/range__error_step_zero.py +0 -2
  805. package/ref-monty/crates/monty/test_cases/range__error_too_many_args.py +0 -2
  806. package/ref-monty/crates/monty/test_cases/range__getitem_index_error.py +0 -10
  807. package/ref-monty/crates/monty/test_cases/range__ops.py +0 -236
  808. package/ref-monty/crates/monty/test_cases/re__basic.py +0 -756
  809. package/ref-monty/crates/monty/test_cases/re__grouping.py +0 -241
  810. package/ref-monty/crates/monty/test_cases/re__match.py +0 -148
  811. package/ref-monty/crates/monty/test_cases/recursion__deep_drop.py +0 -26
  812. package/ref-monty/crates/monty/test_cases/recursion__deep_eq.py +0 -23
  813. package/ref-monty/crates/monty/test_cases/recursion__deep_hash.py +0 -46
  814. package/ref-monty/crates/monty/test_cases/recursion__deep_repr.py +0 -12
  815. package/ref-monty/crates/monty/test_cases/recursion__function_depth.py +0 -13
  816. package/ref-monty/crates/monty/test_cases/refcount__cycle_mutual_reference.py +0 -18
  817. package/ref-monty/crates/monty/test_cases/refcount__cycle_self_reference.py +0 -12
  818. package/ref-monty/crates/monty/test_cases/refcount__dict_basic.py +0 -5
  819. package/ref-monty/crates/monty/test_cases/refcount__dict_get.py +0 -5
  820. package/ref-monty/crates/monty/test_cases/refcount__dict_keys_and.py +0 -14
  821. package/ref-monty/crates/monty/test_cases/refcount__dict_overwrite.py +0 -6
  822. package/ref-monty/crates/monty/test_cases/refcount__gather_cleanup.py +0 -16
  823. package/ref-monty/crates/monty/test_cases/refcount__gather_exception.py +0 -18
  824. package/ref-monty/crates/monty/test_cases/refcount__gather_nested_cancel.py +0 -25
  825. package/ref-monty/crates/monty/test_cases/refcount__immediate_skipped.py +0 -4
  826. package/ref-monty/crates/monty/test_cases/refcount__kwargs_unpacking.py +0 -27
  827. package/ref-monty/crates/monty/test_cases/refcount__list_append_multiple.py +0 -6
  828. package/ref-monty/crates/monty/test_cases/refcount__list_append_ref.py +0 -5
  829. package/ref-monty/crates/monty/test_cases/refcount__list_concat.py +0 -5
  830. package/ref-monty/crates/monty/test_cases/refcount__list_getitem.py +0 -5
  831. package/ref-monty/crates/monty/test_cases/refcount__list_iadd.py +0 -5
  832. package/ref-monty/crates/monty/test_cases/refcount__nested_list.py +0 -4
  833. package/ref-monty/crates/monty/test_cases/refcount__re_pattern_sub_error_paths.py +0 -37
  834. package/ref-monty/crates/monty/test_cases/refcount__re_search_match.py +0 -34
  835. package/ref-monty/crates/monty/test_cases/refcount__re_sub_error_paths.py +0 -31
  836. package/ref-monty/crates/monty/test_cases/refcount__shared_reference.py +0 -4
  837. package/ref-monty/crates/monty/test_cases/refcount__single_list.py +0 -3
  838. package/ref-monty/crates/monty/test_cases/repr__cycle_detection.py +0 -24
  839. package/ref-monty/crates/monty/test_cases/set__ops.py +0 -191
  840. package/ref-monty/crates/monty/test_cases/set__review_bugs.py +0 -35
  841. package/ref-monty/crates/monty/test_cases/set__unpack_type_error.py +0 -2
  842. package/ref-monty/crates/monty/test_cases/slice__invalid_indices.py +0 -2
  843. package/ref-monty/crates/monty/test_cases/slice__kwargs.py +0 -9
  844. package/ref-monty/crates/monty/test_cases/slice__no_args.py +0 -9
  845. package/ref-monty/crates/monty/test_cases/slice__ops.py +0 -149
  846. package/ref-monty/crates/monty/test_cases/slice__step_zero.py +0 -9
  847. package/ref-monty/crates/monty/test_cases/slice__step_zero_bytes.py +0 -9
  848. package/ref-monty/crates/monty/test_cases/slice__step_zero_range.py +0 -9
  849. package/ref-monty/crates/monty/test_cases/slice__step_zero_str.py +0 -9
  850. package/ref-monty/crates/monty/test_cases/slice__step_zero_tuple.py +0 -9
  851. package/ref-monty/crates/monty/test_cases/slice__too_many_args.py +0 -9
  852. package/ref-monty/crates/monty/test_cases/str__getitem_index_error.py +0 -10
  853. package/ref-monty/crates/monty/test_cases/str__index_not_found.py +0 -9
  854. package/ref-monty/crates/monty/test_cases/str__join_no_args.py +0 -9
  855. package/ref-monty/crates/monty/test_cases/str__join_non_string.py +0 -9
  856. package/ref-monty/crates/monty/test_cases/str__join_not_iterable.py +0 -9
  857. package/ref-monty/crates/monty/test_cases/str__join_too_many_args.py +0 -9
  858. package/ref-monty/crates/monty/test_cases/str__methods.py +0 -327
  859. package/ref-monty/crates/monty/test_cases/str__ops.py +0 -162
  860. package/ref-monty/crates/monty/test_cases/str__partition_empty.py +0 -9
  861. package/ref-monty/crates/monty/test_cases/str__rsplit_empty_sep.py +0 -9
  862. package/ref-monty/crates/monty/test_cases/str__split_empty_sep.py +0 -9
  863. package/ref-monty/crates/monty/test_cases/sys__types.py +0 -7
  864. package/ref-monty/crates/monty/test_cases/traceback__division_error.py +0 -30
  865. package/ref-monty/crates/monty/test_cases/traceback__index_error.py +0 -17
  866. package/ref-monty/crates/monty/test_cases/traceback__insert_as_int.py +0 -10
  867. package/ref-monty/crates/monty/test_cases/traceback__nested_call.py +0 -29
  868. package/ref-monty/crates/monty/test_cases/traceback__nonlocal_module_scope.py +0 -10
  869. package/ref-monty/crates/monty/test_cases/traceback__nonlocal_unbound.py +0 -24
  870. package/ref-monty/crates/monty/test_cases/traceback__range_as_int.py +0 -9
  871. package/ref-monty/crates/monty/test_cases/traceback__recursion_error.py +0 -23
  872. package/ref-monty/crates/monty/test_cases/traceback__set_mutation.py +0 -11
  873. package/ref-monty/crates/monty/test_cases/traceback__undefined_attr_call.py +0 -16
  874. package/ref-monty/crates/monty/test_cases/traceback__undefined_call.py +0 -16
  875. package/ref-monty/crates/monty/test_cases/traceback__undefined_raise.py +0 -16
  876. package/ref-monty/crates/monty/test_cases/try_except__all.py +0 -472
  877. package/ref-monty/crates/monty/test_cases/try_except__bare_raise_no_context.py +0 -2
  878. package/ref-monty/crates/monty/test_cases/try_except__invalid_type.py +0 -5
  879. package/ref-monty/crates/monty/test_cases/tuple__getitem_out_of_bounds.py +0 -3
  880. package/ref-monty/crates/monty/test_cases/tuple__index_not_found.py +0 -9
  881. package/ref-monty/crates/monty/test_cases/tuple__index_start_gt_end.py +0 -10
  882. package/ref-monty/crates/monty/test_cases/tuple__methods.py +0 -19
  883. package/ref-monty/crates/monty/test_cases/tuple__ops.py +0 -133
  884. package/ref-monty/crates/monty/test_cases/tuple__unpack_type_error.py +0 -2
  885. package/ref-monty/crates/monty/test_cases/type__builtin_attr_error.py +0 -9
  886. package/ref-monty/crates/monty/test_cases/type__bytes_negative.py +0 -2
  887. package/ref-monty/crates/monty/test_cases/type__cell_not_builtin.py +0 -9
  888. package/ref-monty/crates/monty/test_cases/type__exception_attr_error.py +0 -11
  889. package/ref-monty/crates/monty/test_cases/type__float_conversion_error.py +0 -2
  890. package/ref-monty/crates/monty/test_cases/type__float_repr_both_quotes.py +0 -9
  891. package/ref-monty/crates/monty/test_cases/type__float_repr_newline.py +0 -9
  892. package/ref-monty/crates/monty/test_cases/type__float_repr_single_quote.py +0 -9
  893. package/ref-monty/crates/monty/test_cases/type__int_conversion_error.py +0 -2
  894. package/ref-monty/crates/monty/test_cases/type__list_not_iterable.py +0 -2
  895. package/ref-monty/crates/monty/test_cases/type__non_builtin_name_error.py +0 -9
  896. package/ref-monty/crates/monty/test_cases/type__ops.py +0 -200
  897. package/ref-monty/crates/monty/test_cases/type__shadow_exc.py +0 -3
  898. package/ref-monty/crates/monty/test_cases/type__shadow_int.py +0 -9
  899. package/ref-monty/crates/monty/test_cases/type__shadow_len.py +0 -3
  900. package/ref-monty/crates/monty/test_cases/type__tuple_not_iterable.py +0 -2
  901. package/ref-monty/crates/monty/test_cases/type_error__int_add_list.py +0 -2
  902. package/ref-monty/crates/monty/test_cases/type_error__int_div_str.py +0 -2
  903. package/ref-monty/crates/monty/test_cases/type_error__int_floordiv_str.py +0 -2
  904. package/ref-monty/crates/monty/test_cases/type_error__int_iadd_str.py +0 -3
  905. package/ref-monty/crates/monty/test_cases/type_error__int_mod_str.py +0 -2
  906. package/ref-monty/crates/monty/test_cases/type_error__int_pow_str.py +0 -2
  907. package/ref-monty/crates/monty/test_cases/type_error__int_sub_str.py +0 -2
  908. package/ref-monty/crates/monty/test_cases/type_error__list_add_int.py +0 -2
  909. package/ref-monty/crates/monty/test_cases/type_error__list_add_str.py +0 -2
  910. package/ref-monty/crates/monty/test_cases/type_error__list_iadd_int.py +0 -6
  911. package/ref-monty/crates/monty/test_cases/type_error__str_add_int.py +0 -2
  912. package/ref-monty/crates/monty/test_cases/type_error__str_iadd_int.py +0 -3
  913. package/ref-monty/crates/monty/test_cases/type_error__unary_invert_str.py +0 -3
  914. package/ref-monty/crates/monty/test_cases/type_error__unary_minus_str.py +0 -4
  915. package/ref-monty/crates/monty/test_cases/type_error__unary_neg_str.py +0 -3
  916. package/ref-monty/crates/monty/test_cases/type_error__unary_plus_str.py +0 -4
  917. package/ref-monty/crates/monty/test_cases/typing__types.py +0 -24
  918. package/ref-monty/crates/monty/test_cases/unpack__nested.py +0 -48
  919. package/ref-monty/crates/monty/test_cases/unpack__non_sequence.py +0 -9
  920. package/ref-monty/crates/monty/test_cases/unpack__not_enough.py +0 -9
  921. package/ref-monty/crates/monty/test_cases/unpack__ops.py +0 -153
  922. package/ref-monty/crates/monty/test_cases/unpack__star_not_enough.py +0 -9
  923. package/ref-monty/crates/monty/test_cases/unpack__too_many.py +0 -9
  924. package/ref-monty/crates/monty/test_cases/version__cpython.py +0 -4
  925. package/ref-monty/crates/monty/test_cases/walrus__all.py +0 -178
  926. package/ref-monty/crates/monty/test_cases/while__all.py +0 -206
  927. package/ref-monty/crates/monty/tests/asyncio.rs +0 -764
  928. package/ref-monty/crates/monty/tests/binary_serde.rs +0 -185
  929. package/ref-monty/crates/monty/tests/bytecode_limits.rs +0 -248
  930. package/ref-monty/crates/monty/tests/datatest_runner.rs +0 -2029
  931. package/ref-monty/crates/monty/tests/inputs.rs +0 -420
  932. package/ref-monty/crates/monty/tests/json_serde.rs +0 -250
  933. package/ref-monty/crates/monty/tests/main.rs +0 -71
  934. package/ref-monty/crates/monty/tests/math_module.rs +0 -114
  935. package/ref-monty/crates/monty/tests/name_lookup.rs +0 -482
  936. package/ref-monty/crates/monty/tests/os_tests.rs +0 -459
  937. package/ref-monty/crates/monty/tests/parse_errors.rs +0 -441
  938. package/ref-monty/crates/monty/tests/print_writer.rs +0 -238
  939. package/ref-monty/crates/monty/tests/py_object.rs +0 -121
  940. package/ref-monty/crates/monty/tests/regex.rs +0 -90
  941. package/ref-monty/crates/monty/tests/repl.rs +0 -344
  942. package/ref-monty/crates/monty/tests/resource_limits.rs +0 -1826
  943. package/ref-monty/crates/monty/tests/try_from.rs +0 -167
  944. package/ref-monty/crates/monty-cli/Cargo.toml +0 -25
  945. package/ref-monty/crates/monty-cli/src/main.rs +0 -541
  946. package/ref-monty/crates/monty-js/.cargo/config.toml +0 -2
  947. package/ref-monty/crates/monty-js/.prettierignore +0 -8
  948. package/ref-monty/crates/monty-js/Cargo.toml +0 -32
  949. package/ref-monty/crates/monty-js/README.md +0 -207
  950. package/ref-monty/crates/monty-js/__test__/async.spec.ts +0 -350
  951. package/ref-monty/crates/monty-js/__test__/basic.spec.ts +0 -114
  952. package/ref-monty/crates/monty-js/__test__/exceptions.spec.ts +0 -427
  953. package/ref-monty/crates/monty-js/__test__/external.spec.ts +0 -354
  954. package/ref-monty/crates/monty-js/__test__/inputs.spec.ts +0 -143
  955. package/ref-monty/crates/monty-js/__test__/limits.spec.ts +0 -162
  956. package/ref-monty/crates/monty-js/__test__/package.json +0 -3
  957. package/ref-monty/crates/monty-js/__test__/print.spec.ts +0 -229
  958. package/ref-monty/crates/monty-js/__test__/repl.spec.ts +0 -34
  959. package/ref-monty/crates/monty-js/__test__/serialize.spec.ts +0 -205
  960. package/ref-monty/crates/monty-js/__test__/start.spec.ts +0 -443
  961. package/ref-monty/crates/monty-js/__test__/type_check.spec.ts +0 -147
  962. package/ref-monty/crates/monty-js/__test__/types.spec.ts +0 -319
  963. package/ref-monty/crates/monty-js/build.rs +0 -61
  964. package/ref-monty/crates/monty-js/index-header.d.ts +0 -3
  965. package/ref-monty/crates/monty-js/package-lock.json +0 -4694
  966. package/ref-monty/crates/monty-js/package.json +0 -100
  967. package/ref-monty/crates/monty-js/scripts/smoke-test.sh +0 -69
  968. package/ref-monty/crates/monty-js/smoke-test/package.json +0 -17
  969. package/ref-monty/crates/monty-js/smoke-test/test.ts +0 -171
  970. package/ref-monty/crates/monty-js/smoke-test/tsconfig.json +0 -11
  971. package/ref-monty/crates/monty-js/src/convert.rs +0 -648
  972. package/ref-monty/crates/monty-js/src/exceptions.rs +0 -293
  973. package/ref-monty/crates/monty-js/src/lib.rs +0 -41
  974. package/ref-monty/crates/monty-js/src/limits.rs +0 -53
  975. package/ref-monty/crates/monty-js/src/monty_cls.rs +0 -1407
  976. package/ref-monty/crates/monty-js/tsconfig.json +0 -17
  977. package/ref-monty/crates/monty-js/wrapper.ts +0 -701
  978. package/ref-monty/crates/monty-python/Cargo.toml +0 -38
  979. package/ref-monty/crates/monty-python/README.md +0 -134
  980. package/ref-monty/crates/monty-python/build.rs +0 -4
  981. package/ref-monty/crates/monty-python/example.py +0 -40
  982. package/ref-monty/crates/monty-python/exercise.py +0 -46
  983. package/ref-monty/crates/monty-python/pyproject.toml +0 -57
  984. package/ref-monty/crates/monty-python/python/pydantic_monty/__init__.py +0 -281
  985. package/ref-monty/crates/monty-python/python/pydantic_monty/_monty.pyi +0 -677
  986. package/ref-monty/crates/monty-python/python/pydantic_monty/os_access.py +0 -933
  987. package/ref-monty/crates/monty-python/python/pydantic_monty/py.typed +0 -0
  988. package/ref-monty/crates/monty-python/src/convert.rs +0 -273
  989. package/ref-monty/crates/monty-python/src/dataclass.rs +0 -461
  990. package/ref-monty/crates/monty-python/src/exceptions.rs +0 -557
  991. package/ref-monty/crates/monty-python/src/external.rs +0 -165
  992. package/ref-monty/crates/monty-python/src/lib.rs +0 -77
  993. package/ref-monty/crates/monty-python/src/limits.rs +0 -142
  994. package/ref-monty/crates/monty-python/src/monty_cls.rs +0 -1650
  995. package/ref-monty/crates/monty-python/src/repl.rs +0 -470
  996. package/ref-monty/crates/monty-python/src/serialization.rs +0 -761
  997. package/ref-monty/crates/monty-python/tests/test_async.py +0 -1201
  998. package/ref-monty/crates/monty-python/tests/test_basic.py +0 -66
  999. package/ref-monty/crates/monty-python/tests/test_dataclasses.py +0 -971
  1000. package/ref-monty/crates/monty-python/tests/test_exceptions.py +0 -361
  1001. package/ref-monty/crates/monty-python/tests/test_external.py +0 -367
  1002. package/ref-monty/crates/monty-python/tests/test_inputs.py +0 -126
  1003. package/ref-monty/crates/monty-python/tests/test_limits.py +0 -257
  1004. package/ref-monty/crates/monty-python/tests/test_os_access.py +0 -1286
  1005. package/ref-monty/crates/monty-python/tests/test_os_access_compat.py +0 -731
  1006. package/ref-monty/crates/monty-python/tests/test_os_access_raw.py +0 -483
  1007. package/ref-monty/crates/monty-python/tests/test_os_calls.py +0 -819
  1008. package/ref-monty/crates/monty-python/tests/test_print.py +0 -208
  1009. package/ref-monty/crates/monty-python/tests/test_re.py +0 -170
  1010. package/ref-monty/crates/monty-python/tests/test_readme_examples.py +0 -20
  1011. package/ref-monty/crates/monty-python/tests/test_repl.py +0 -749
  1012. package/ref-monty/crates/monty-python/tests/test_serialize.py +0 -284
  1013. package/ref-monty/crates/monty-python/tests/test_start.py +0 -346
  1014. package/ref-monty/crates/monty-python/tests/test_threading.py +0 -163
  1015. package/ref-monty/crates/monty-python/tests/test_type_check.py +0 -344
  1016. package/ref-monty/crates/monty-python/tests/test_types.py +0 -553
  1017. package/ref-monty/crates/monty-type-checking/Cargo.toml +0 -32
  1018. package/ref-monty/crates/monty-type-checking/src/db.rs +0 -116
  1019. package/ref-monty/crates/monty-type-checking/src/lib.rs +0 -4
  1020. package/ref-monty/crates/monty-type-checking/src/type_check.rs +0 -280
  1021. package/ref-monty/crates/monty-type-checking/tests/bad_types.py +0 -109
  1022. package/ref-monty/crates/monty-type-checking/tests/bad_types_output.txt +0 -21
  1023. package/ref-monty/crates/monty-type-checking/tests/good_types.py +0 -475
  1024. package/ref-monty/crates/monty-type-checking/tests/main.rs +0 -205
  1025. package/ref-monty/crates/monty-type-checking/tests/reveal_types.py +0 -56
  1026. package/ref-monty/crates/monty-type-checking/tests/reveal_types_output.txt +0 -41
  1027. package/ref-monty/crates/monty-typeshed/Cargo.toml +0 -29
  1028. package/ref-monty/crates/monty-typeshed/README.md +0 -11
  1029. package/ref-monty/crates/monty-typeshed/build.rs +0 -101
  1030. package/ref-monty/crates/monty-typeshed/custom/README.md +0 -1
  1031. package/ref-monty/crates/monty-typeshed/custom/asyncio.pyi +0 -138
  1032. package/ref-monty/crates/monty-typeshed/custom/os.pyi +0 -87
  1033. package/ref-monty/crates/monty-typeshed/custom/sys.pyi +0 -33
  1034. package/ref-monty/crates/monty-typeshed/src/lib.rs +0 -56
  1035. package/ref-monty/crates/monty-typeshed/update.py +0 -321
  1036. package/ref-monty/crates/monty-typeshed/vendor/typeshed/source_commit.txt +0 -1
  1037. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/VERSIONS +0 -20
  1038. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/_collections_abc.pyi +0 -105
  1039. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/_typeshed/__init__.pyi +0 -394
  1040. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/asyncio.pyi +0 -138
  1041. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/builtins.pyi +0 -1434
  1042. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/collections/__init__.pyi +0 -527
  1043. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/collections/abc.pyi +0 -2
  1044. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/dataclasses.pyi +0 -502
  1045. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/enum.pyi +0 -376
  1046. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/math.pyi +0 -149
  1047. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/os.pyi +0 -87
  1048. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/pathlib/__init__.pyi +0 -395
  1049. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/pathlib/types.pyi +0 -8
  1050. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/re.pyi +0 -337
  1051. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/sys.pyi +0 -33
  1052. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/types.pyi +0 -741
  1053. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/typing.pyi +0 -1217
  1054. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/typing_extensions.pyi +0 -716
  1055. package/ref-monty/docs/usage-guide.md +0 -117
  1056. package/ref-monty/examples/README.md +0 -3
  1057. package/ref-monty/examples/expense_analysis/README.md +0 -3
  1058. package/ref-monty/examples/expense_analysis/data.py +0 -124
  1059. package/ref-monty/examples/expense_analysis/main.py +0 -115
  1060. package/ref-monty/examples/sql_playground/README.md +0 -20
  1061. package/ref-monty/examples/sql_playground/external_functions.py +0 -129
  1062. package/ref-monty/examples/sql_playground/main.py +0 -81
  1063. package/ref-monty/examples/sql_playground/sandbox_code.py +0 -82
  1064. package/ref-monty/examples/sql_playground/type_stubs.pyi +0 -14
  1065. package/ref-monty/examples/web_scraper/README.md +0 -15
  1066. package/ref-monty/examples/web_scraper/browser.py +0 -56
  1067. package/ref-monty/examples/web_scraper/example_code.py +0 -59
  1068. package/ref-monty/examples/web_scraper/external_functions.py +0 -324
  1069. package/ref-monty/examples/web_scraper/main.py +0 -193
  1070. package/ref-monty/examples/web_scraper/sub_agent.py +0 -79
  1071. package/ref-monty/monty-npm.md +0 -235
  1072. package/ref-monty/pyproject.toml +0 -162
  1073. package/ref-monty/scripts/check_imports.py +0 -91
  1074. package/ref-monty/scripts/codecov_diff.py +0 -412
  1075. package/ref-monty/scripts/complete_tests.py +0 -146
  1076. package/ref-monty/scripts/flamegraph_to_text.py +0 -208
  1077. package/ref-monty/scripts/iter_test_methods.py +0 -540
  1078. package/ref-monty/scripts/run_traceback.py +0 -180
  1079. package/ref-monty/scripts/startup_performance.py +0 -130
  1080. package/ref-monty/uv.lock +0 -1779
  1081. /package/docs/{plugin-examples.md → plugins-examples.md} +0 -0
@@ -1,2029 +0,0 @@
1
- use std::{
2
- cell::RefCell,
3
- collections::{HashMap, HashSet},
4
- error::Error,
5
- ffi::CString,
6
- fs,
7
- panic::{self, AssertUnwindSafe},
8
- path::Path,
9
- sync::{
10
- OnceLock,
11
- mpsc::{self, RecvTimeoutError},
12
- },
13
- thread,
14
- time::Duration,
15
- };
16
-
17
- use ahash::AHashMap;
18
- use monty::{
19
- ExcType, ExtFunctionResult, LimitedTracker, MontyException, MontyObject, MontyRun, NameLookupResult, OsFunction,
20
- PrintWriter, ResourceLimits, RunProgress, dir_stat, file_stat,
21
- };
22
- use pyo3::{prelude::*, types::PyDict};
23
- use similar::TextDiff;
24
-
25
- /// Recursion limit for test execution.
26
- ///
27
- /// Used for both Monty and CPython tests. CPython needs ~5 extra frames
28
- /// for runpy overhead, which is added in run_file_and_get_traceback.
29
- ///
30
- /// NOTE this value is chosen to avoid both:
31
- /// * other recursion errors in python (if it's too low)
32
- /// * and, stack overflows in debug rust (if it's too high)
33
- const TEST_RECURSION_LIMIT: usize = 50;
34
-
35
- /// Test configuration parsed from directive comments.
36
- ///
37
- /// Parsed from an optional first-line comment like `# xfail=monty,cpython` or `# call-external`.
38
- /// If not present, defaults to running on both interpreters in standard mode.
39
- ///
40
- /// ## Xfail Semantics (Strict)
41
- /// - `xfail=monty` - Test is expected to fail on Monty; if it passes, that's an error
42
- /// - `xfail=cpython` - Test is expected to fail on CPython; if it passes, that's an error
43
- /// - `xfail=monty,cpython` - Expected to fail on both interpreters
44
- #[derive(Debug, Clone, Default)]
45
- #[expect(clippy::struct_excessive_bools)]
46
- struct TestConfig {
47
- /// When true, test is expected to fail on Monty (strict xfail).
48
- xfail_monty: bool,
49
- /// When true, test is expected to fail on CPython (strict xfail).
50
- xfail_cpython: bool,
51
- /// When true, use MontyRun with external function support instead of MontyRun.
52
- iter_mode: bool,
53
- /// When true, wrap code in async context for CPython execution.
54
- /// Used for tests with top-level await which Monty supports but CPython doesn't.
55
- async_mode: bool,
56
- }
57
-
58
- /// Represents the expected outcome of a test fixture
59
- #[derive(Debug, Clone)]
60
- enum Expectation {
61
- /// Expect exception (parse-time or runtime) with specific message
62
- Raise(String),
63
- /// Expect successful execution, check py_str() output
64
- ReturnStr(String),
65
- /// Expect successful execution, check py_repr() output
66
- Return(String),
67
- /// Expect successful execution, check py_type() output
68
- ReturnType(String),
69
- /// Expect successful execution, check ref counts of named variables.
70
- /// Only used when `ref-count-return` feature is enabled; skipped otherwise.
71
- RefCounts(#[cfg_attr(not(feature = "ref-count-return"), expect(dead_code))] AHashMap<String, usize>),
72
- /// Expect exception with full traceback comparison.
73
- /// The expected traceback string should match exactly between Monty and CPython.
74
- Traceback(String),
75
- /// Expect successful execution without raising an exception (no return value check).
76
- /// Used for tests that rely on asserts or just verify code runs.
77
- NoException,
78
- }
79
-
80
- impl Expectation {
81
- /// Returns the expected value string
82
- fn expected_value(&self) -> &str {
83
- match self {
84
- Self::Raise(s) | Self::ReturnStr(s) | Self::Return(s) | Self::ReturnType(s) | Self::Traceback(s) => s,
85
- Self::RefCounts(_) | Self::NoException => "",
86
- }
87
- }
88
- }
89
-
90
- /// Parse a Python fixture file into code, expected outcome, and test configuration.
91
- ///
92
- /// The file may optionally contain a `# xfail=monty,cpython` comment to specify
93
- /// which interpreters the test is expected to fail on. If not present, defaults to
94
- /// running on both and expecting success.
95
- ///
96
- /// The file may have an expectation comment as the LAST line:
97
- /// - `# Raise=ExceptionType('message')` - Exception (parse-time or runtime)
98
- /// - `# Return.str=value` - Check py_str() output
99
- /// - `# Return=value` - Check py_repr() output
100
- /// - `# Return.type=typename` - Check py_type() output
101
- /// - `# ref-counts={'var': count, ...}` - Check ref counts of named heap variables
102
- ///
103
- /// Or a traceback expectation as a triple-quoted string at the end (uses actual test filename):
104
- /// ```text
105
- /// """TRACEBACK:
106
- /// Traceback (most recent call last):
107
- /// File "my_test.py", line 4, in <module>
108
- /// foo()
109
- /// ValueError: message
110
- /// """
111
- /// ```
112
- ///
113
- /// If no expectation comment is present, the test just verifies the code runs without exception.
114
- fn parse_fixture(content: &str) -> (String, Expectation, TestConfig) {
115
- let lines: Vec<&str> = content.lines().collect();
116
-
117
- assert!(!lines.is_empty(), "Empty fixture file");
118
-
119
- // comment lines with leading # and spaces stripped
120
- let comment_lines = lines
121
- .iter()
122
- .filter(|line| line.starts_with('#'))
123
- .map(|line| line.trim_start_matches('#').trim())
124
- .collect::<Vec<_>>();
125
-
126
- let mut config = TestConfig {
127
- iter_mode: comment_lines.iter().any(|line| line.starts_with("call-external")),
128
- async_mode: comment_lines.iter().any(|line| line.starts_with("run-async")),
129
- ..Default::default()
130
- };
131
- // Check for "xfail=" directive
132
- if let Some(&xfail_line) = comment_lines.iter().find(|line| line.starts_with("xfail=")) {
133
- // Parse until whitespace or end of line
134
- let xfail_end = xfail_line.find(|c: char| c.is_whitespace()).unwrap_or(xfail_line.len());
135
- let xfail_str = &xfail_line[..xfail_end];
136
- config.xfail_monty = xfail_str.contains("monty");
137
- config.xfail_cpython = xfail_str.contains("cpython");
138
- }
139
-
140
- // Check for TRACEBACK expectation (triple-quoted string at end of file)
141
- // Format: """TRACEBACK:\n...\n"""
142
- if let Some((code, traceback)) = parse_traceback_expectation(content) {
143
- return (code, Expectation::Traceback(traceback), config);
144
- }
145
-
146
- // Get the last line and check if it's an expectation comment
147
- let last_line = lines.last().unwrap();
148
-
149
- // Parse expectation from comment line if present
150
- // Note: Check more specific patterns first (Return.str, Return.type, ref-counts) before general Return
151
- let (expectation, code_lines) = if let Some(expected) = last_line.strip_prefix("# ref-counts=") {
152
- (
153
- Expectation::RefCounts(parse_ref_counts(expected)),
154
- &lines[..lines.len() - 1],
155
- )
156
- } else if let Some(expected) = last_line.strip_prefix("# Return.str=") {
157
- (Expectation::ReturnStr(expected.to_string()), &lines[..lines.len() - 1])
158
- } else if let Some(expected) = last_line.strip_prefix("# Return.type=") {
159
- (Expectation::ReturnType(expected.to_string()), &lines[..lines.len() - 1])
160
- } else if let Some(expected) = last_line.strip_prefix("# Return=") {
161
- (Expectation::Return(expected.to_string()), &lines[..lines.len() - 1])
162
- } else if let Some(expected) = last_line.strip_prefix("# Raise=") {
163
- (Expectation::Raise(expected.to_string()), &lines[..lines.len() - 1])
164
- } else {
165
- // No expectation comment - just run and check it doesn't raise
166
- (Expectation::NoException, &lines[..])
167
- };
168
-
169
- // Code is everything except the directive comment (and expectation comment if present)
170
- let code = code_lines.join("\n");
171
-
172
- (code, expectation, config)
173
- }
174
-
175
- /// Parses a TRACEBACK expectation from the end of a fixture file.
176
- ///
177
- /// Looks for a triple-quoted string starting with `"""TRACEBACK:` at the end of the file.
178
- /// Returns `Some((code, expected_traceback))` if found, `None` otherwise.
179
- ///
180
- /// The traceback string should contain the full expected output including the
181
- /// "Traceback (most recent call last):" header and the exception line.
182
- fn parse_traceback_expectation(content: &str) -> Option<(String, String)> {
183
- // Format: """\nTRACEBACK:\n...\n"""
184
- const MARKER: &str = "\"\"\"\nTRACEBACK:\n";
185
-
186
- // Find the TRACEBACK marker
187
- let marker_pos = content.find(MARKER)?;
188
-
189
- // Extract the code before the marker
190
- let code_part = &content[..marker_pos];
191
- let lines: Vec<&str> = code_part.lines().collect();
192
- let code = lines.join("\n").trim_end().to_string();
193
-
194
- // Extract the traceback content between the markers
195
- let after_marker = &content[marker_pos + MARKER.len()..];
196
-
197
- // Find the closing triple quotes (preceded by newline)
198
- let end_pos = after_marker.find("\n\"\"\"")?;
199
- let traceback_content = &after_marker[..end_pos];
200
-
201
- Some((code, traceback_content.to_string()))
202
- }
203
-
204
- /// Parses the ref-counts format: {'var': count, 'var2': count2}
205
- ///
206
- /// Supports both single and double quotes for variable names.
207
- /// Example: {'x': 2, 'y': 1} or {"x": 2, "y": 1}
208
- fn parse_ref_counts(s: &str) -> AHashMap<String, usize> {
209
- let mut counts = AHashMap::new();
210
- let trimmed = s.trim().trim_start_matches('{').trim_end_matches('}');
211
- for pair in trimmed.split(',') {
212
- let pair = pair.trim();
213
- if pair.is_empty() {
214
- continue;
215
- }
216
- let parts: Vec<&str> = pair.split(':').collect();
217
- assert!(
218
- parts.len() == 2,
219
- "Invalid ref-counts pair format: {pair}. Expected 'name': count"
220
- );
221
- let name = parts[0].trim().trim_matches('\'').trim_matches('"');
222
- let count: usize = parts[1]
223
- .trim()
224
- .parse()
225
- .unwrap_or_else(|_| panic!("Invalid ref count value: {}", parts[1]));
226
- counts.insert(name.to_string(), count);
227
- }
228
- counts
229
- }
230
-
231
- /// Python implementations of external functions for running iter mode tests in CPython.
232
- ///
233
- /// These implementations mirror the behavior of `dispatch_external_call` so that
234
- /// iter mode tests produce identical results in both Monty and CPython.
235
- ///
236
- /// This is loaded from `scripts/iter_test_methods.py` which is also imported by
237
- /// `scripts/run_traceback.py` to ensure consistency.
238
- const ITER_EXT_FUNCTIONS_PYTHON: &str = include_str!("../../../scripts/iter_test_methods.py");
239
-
240
- /// Pre-imports Python modules that can cause race conditions during parallel test execution.
241
- ///
242
- /// Python's import machinery isn't fully thread-safe during module initialization.
243
- /// When multiple tests try to import modules like `typing` or `dataclasses` simultaneously,
244
- /// one thread may see a partially initialized module, causing `AttributeError`.
245
- ///
246
- /// This function must be called once before any parallel test execution to ensure
247
- /// all relevant modules are fully initialized.
248
- fn ensure_python_modules_imported() {
249
- static INIT: OnceLock<()> = OnceLock::new();
250
- INIT.get_or_init(|| {
251
- Python::attach(|py| {
252
- // Import modules that are used by iter_test_methods.py and can cause race conditions.
253
- // The order matters: import dependencies first.
254
- py.import("typing").expect("Failed to import typing");
255
- py.import("dataclasses").expect("Failed to import dataclasses");
256
- py.import("pathlib").expect("Failed to import pathlib");
257
- py.import("stat").expect("Failed to import stat");
258
- py.import("asyncio").expect("Failed to import asyncio");
259
- py.import("traceback").expect("Failed to import traceback");
260
-
261
- // Also pre-execute the iter_test_methods code once to ensure all its
262
- // module-level code (dataclass definitions, monkey-patches) is initialized
263
- let ext_funcs_cstr = CString::new(ITER_EXT_FUNCTIONS_PYTHON).expect("Invalid C string");
264
- py.run(&ext_funcs_cstr, None, None)
265
- .expect("Failed to pre-initialize iter_test_methods");
266
- });
267
- });
268
- }
269
-
270
- /// Result from dispatching an external function call.
271
- ///
272
- /// Distinguishes between synchronous calls (return immediately) and
273
- /// asynchronous calls (return a future that needs later resolution).
274
- enum DispatchResult {
275
- /// Synchronous result - pass directly to `state.run()`.
276
- Sync(ExtFunctionResult),
277
- /// Asynchronous call - use `state.run_pending()` and resolve later.
278
- /// Contains the value to resolve the future with.
279
- Async(MontyObject),
280
- }
281
-
282
- /// Dispatches an external function call to the appropriate test implementation.
283
- ///
284
- /// Returns `DispatchResult::Sync` for synchronous calls or `DispatchResult::Async`
285
- /// for coroutine calls that should use `run_pending()`.
286
- ///
287
- /// # Panics
288
- /// Panics if the function name is unknown or arguments are invalid types.
289
- fn dispatch_external_call(name: &str, args: Vec<MontyObject>) -> DispatchResult {
290
- match name {
291
- "add_ints" => {
292
- assert!(args.len() == 2, "add_ints requires 2 arguments");
293
- let a = i64::try_from(&args[0]).expect("add_ints: first arg must be int");
294
- let b = i64::try_from(&args[1]).expect("add_ints: second arg must be int");
295
- DispatchResult::Sync(MontyObject::Int(a + b).into())
296
- }
297
- "concat_strings" => {
298
- assert!(args.len() == 2, "concat_strings requires 2 arguments");
299
- let a = String::try_from(&args[0]).expect("concat_strings: first arg must be str");
300
- let b = String::try_from(&args[1]).expect("concat_strings: second arg must be str");
301
- DispatchResult::Sync(MontyObject::String(a + &b).into())
302
- }
303
- "return_value" => {
304
- assert!(args.len() == 1, "return_value requires 1 argument");
305
- DispatchResult::Sync(args.into_iter().next().unwrap().into())
306
- }
307
- "get_list" => {
308
- assert!(args.is_empty(), "get_list requires no arguments");
309
- DispatchResult::Sync(
310
- MontyObject::List(vec![MontyObject::Int(1), MontyObject::Int(2), MontyObject::Int(3)]).into(),
311
- )
312
- }
313
- "raise_error" => {
314
- // raise_error(exc_type: str, message: str) -> raises exception
315
- assert!(args.len() == 2, "raise_error requires 2 arguments");
316
- let exc_type_str = String::try_from(&args[0]).expect("raise_error: first arg must be str");
317
- let message = String::try_from(&args[1]).expect("raise_error: second arg must be str");
318
- let exc_type = match exc_type_str.as_str() {
319
- "ValueError" => ExcType::ValueError,
320
- "TypeError" => ExcType::TypeError,
321
- "KeyError" => ExcType::KeyError,
322
- "RuntimeError" => ExcType::RuntimeError,
323
- _ => panic!("raise_error: unsupported exception type: {exc_type_str}"),
324
- };
325
- DispatchResult::Sync(MontyException::new(exc_type, Some(message)).into())
326
- }
327
- "make_point" => {
328
- assert!(args.is_empty(), "make_point requires no arguments");
329
- // Return an immutable Point(x=1, y=2) dataclass
330
- DispatchResult::Sync(
331
- MontyObject::Dataclass {
332
- name: "Point".to_string(),
333
- type_id: 0, // Test fixture has no real Python type
334
- field_names: vec!["x".to_string(), "y".to_string()],
335
- attrs: vec![
336
- (MontyObject::String("x".to_string()), MontyObject::Int(1)),
337
- (MontyObject::String("y".to_string()), MontyObject::Int(2)),
338
- ]
339
- .into(),
340
-
341
- frozen: true,
342
- }
343
- .into(),
344
- )
345
- }
346
- "make_mutable_point" => {
347
- assert!(args.is_empty(), "make_mutable_point requires no arguments");
348
- // Return a mutable Point(x=1, y=2) dataclass
349
- DispatchResult::Sync(
350
- MontyObject::Dataclass {
351
- name: "MutablePoint".to_string(),
352
- type_id: 0, // Test fixture has no real Python type
353
- field_names: vec!["x".to_string(), "y".to_string()],
354
- attrs: vec![
355
- (MontyObject::String("x".to_string()), MontyObject::Int(1)),
356
- (MontyObject::String("y".to_string()), MontyObject::Int(2)),
357
- ]
358
- .into(),
359
-
360
- frozen: false,
361
- }
362
- .into(),
363
- )
364
- }
365
- "make_user" => {
366
- assert!(args.len() == 1, "make_user requires 1 argument");
367
- let name = String::try_from(&args[0]).expect("make_user: first arg must be str");
368
- // Return an immutable User(name=name, active=True) dataclass
369
- DispatchResult::Sync(
370
- MontyObject::Dataclass {
371
- name: "User".to_string(),
372
- type_id: 0, // Test fixture has no real Python type
373
- field_names: vec!["name".to_string(), "active".to_string()],
374
- attrs: vec![
375
- (MontyObject::String("name".to_string()), MontyObject::String(name)),
376
- (MontyObject::String("active".to_string()), MontyObject::Bool(true)),
377
- ]
378
- .into(),
379
-
380
- frozen: true,
381
- }
382
- .into(),
383
- )
384
- }
385
- "make_empty" => {
386
- assert!(args.is_empty(), "make_empty requires no arguments");
387
- // Return an immutable empty dataclass with no fields
388
- DispatchResult::Sync(
389
- MontyObject::Dataclass {
390
- name: "Empty".to_string(),
391
- type_id: 0, // Test fixture has no real Python type
392
- field_names: vec![],
393
- attrs: vec![].into(),
394
-
395
- frozen: true,
396
- }
397
- .into(),
398
- )
399
- }
400
- "async_call" => {
401
- // async_call(x) -> coroutine that returns x
402
- // This is an async function - use run_pending() and resolve later
403
- assert!(args.len() == 1, "async_call requires 1 argument");
404
- DispatchResult::Async(args.into_iter().next().unwrap())
405
- }
406
- _ => panic!("Unknown external function: {name}"),
407
- }
408
- }
409
-
410
- /// Dispatches a dataclass method call to the appropriate test implementation.
411
- ///
412
- /// The first argument is always the dataclass instance (`self`). Known methods
413
- /// are implemented to mirror the Python dataclass methods in `iter_test_methods.py`.
414
- /// Unknown methods return `AttributeError`.
415
- fn dispatch_method_call(
416
- method_name: &str,
417
- args: &[MontyObject],
418
- kwargs: &[(MontyObject, MontyObject)],
419
- ) -> ExtFunctionResult {
420
- let class_name = match args.first() {
421
- Some(MontyObject::Dataclass { name, .. }) => name.as_str(),
422
- _ => "<unknown>",
423
- };
424
-
425
- match (class_name, method_name) {
426
- // Point.sum(self) -> int
427
- ("Point" | "MutablePoint", "sum") => {
428
- let (x, y) = extract_point_fields(&args[0]);
429
- MontyObject::Int(x + y).into()
430
- }
431
- // Point.add(self, dx, dy) -> Point
432
- ("Point", "add") => {
433
- assert!(args.len() == 3, "Point.add requires self, dx, dy");
434
- let (x, y) = extract_point_fields(&args[0]);
435
- let dx = i64::try_from(&args[1]).expect("dx must be int");
436
- let dy = i64::try_from(&args[2]).expect("dy must be int");
437
- MontyObject::Dataclass {
438
- name: "Point".to_string(),
439
- type_id: 0,
440
- field_names: vec!["x".to_string(), "y".to_string()],
441
- attrs: vec![
442
- (MontyObject::String("x".to_string()), MontyObject::Int(x + dx)),
443
- (MontyObject::String("y".to_string()), MontyObject::Int(y + dy)),
444
- ]
445
- .into(),
446
- frozen: true,
447
- }
448
- .into()
449
- }
450
- // Point.scale(self, factor) -> Point
451
- ("Point", "scale") => {
452
- assert!(args.len() == 2, "Point.scale requires self, factor");
453
- let (x, y) = extract_point_fields(&args[0]);
454
- let factor = i64::try_from(&args[1]).expect("factor must be int");
455
- MontyObject::Dataclass {
456
- name: "Point".to_string(),
457
- type_id: 0,
458
- field_names: vec!["x".to_string(), "y".to_string()],
459
- attrs: vec![
460
- (MontyObject::String("x".to_string()), MontyObject::Int(x * factor)),
461
- (MontyObject::String("y".to_string()), MontyObject::Int(y * factor)),
462
- ]
463
- .into(),
464
- frozen: true,
465
- }
466
- .into()
467
- }
468
- // Point.describe(self, label='point') -> str
469
- ("Point", "describe") => {
470
- let (x, y) = extract_point_fields(&args[0]);
471
- // Check positional arg first, then kwargs, then default
472
- let label = if args.len() > 1 {
473
- String::try_from(&args[1]).expect("label must be str")
474
- } else if let Some(kw_label) = get_kwarg_str(kwargs, "label") {
475
- kw_label
476
- } else {
477
- "point".to_string()
478
- };
479
- MontyObject::String(format!("{label}({x}, {y})")).into()
480
- }
481
- // MutablePoint.shift(self, dx, dy) -> None (mutates in-place via host)
482
- // Note: In the test runner, we can't actually mutate the dataclass in-place
483
- // since the host doesn't have direct heap access. Return None as the method
484
- // would in Python (the mutation happens inside Python's method body).
485
- // For test coverage purposes, we just return None.
486
- ("MutablePoint", "shift") => MontyObject::None.into(),
487
- // User.greeting(self) -> str
488
- ("User", "greeting") => {
489
- let name = extract_user_name(&args[0]);
490
- MontyObject::String(format!("Hello, {name}!")).into()
491
- }
492
- // Unknown method — return AttributeError
493
- _ => {
494
- let message = format!("'{class_name}' object has no attribute '{method_name}'");
495
- MontyException::new(ExcType::AttributeError, Some(message)).into()
496
- }
497
- }
498
- }
499
-
500
- /// Extracts (x, y) fields from a Point or MutablePoint `MontyObject::Dataclass`.
501
- fn extract_point_fields(obj: &MontyObject) -> (i64, i64) {
502
- match obj {
503
- MontyObject::Dataclass { attrs, .. } => {
504
- let mut x = 0i64;
505
- let mut y = 0i64;
506
- for (key, value) in attrs {
507
- if let MontyObject::String(k) = key {
508
- match k.as_str() {
509
- "x" => x = i64::try_from(value).expect("x must be int"),
510
- "y" => y = i64::try_from(value).expect("y must be int"),
511
- _ => {}
512
- }
513
- }
514
- }
515
- (x, y)
516
- }
517
- other => panic!("Expected Dataclass, got {other:?}"),
518
- }
519
- }
520
-
521
- /// Extracts a string kwarg value by key name.
522
- fn get_kwarg_str(kwargs: &[(MontyObject, MontyObject)], name: &str) -> Option<String> {
523
- for (key, value) in kwargs {
524
- if let MontyObject::String(key_str) = key
525
- && key_str == name
526
- {
527
- return Some(String::try_from(value).expect("kwarg value must be str"));
528
- }
529
- }
530
- None
531
- }
532
-
533
- /// Extracts the `name` field from a User `MontyObject::Dataclass`.
534
- fn extract_user_name(obj: &MontyObject) -> String {
535
- match obj {
536
- MontyObject::Dataclass { attrs, .. } => {
537
- for (key, value) in attrs {
538
- if let MontyObject::String(k) = key
539
- && k == "name"
540
- {
541
- return String::try_from(value).expect("name must be str");
542
- }
543
- }
544
- panic!("User dataclass has no 'name' field");
545
- }
546
- other => panic!("Expected Dataclass, got {other:?}"),
547
- }
548
- }
549
-
550
- // =============================================================================
551
- // Virtual Filesystem for OS Call Tests
552
- // =============================================================================
553
-
554
- /// Virtual file entry for OS call tests (static VFS).
555
- struct StaticVirtualFile {
556
- content: &'static [u8],
557
- mode: i64,
558
- }
559
-
560
- /// Virtual file entry (owned, for unified VFS lookups).
561
- struct VirtualFile {
562
- content: Vec<u8>,
563
- mode: i64,
564
- }
565
-
566
- /// Virtual filesystem modification time (arbitrary fixed timestamp).
567
- const VFS_MTIME: f64 = 1_700_000_000.0;
568
-
569
- /// Virtual filesystem for testing Path methods.
570
- ///
571
- /// Structure:
572
- /// ```text
573
- /// /virtual/
574
- /// ├── file.txt (file, 644, "hello world\n")
575
- /// ├── data.bin (file, 644, b"\x00\x01\x02\x03")
576
- /// ├── empty.txt (file, 644, "")
577
- /// ├── subdir/
578
- /// │ ├── nested.txt (file, 644, "nested content")
579
- /// │ └── deep/
580
- /// │ └── file.txt (file, 644, "deep")
581
- /// └── readonly.txt (file, 444, "readonly")
582
- ///
583
- /// /nonexistent (does not exist)
584
- /// ```
585
- fn get_static_virtual_file(path: &str) -> Option<StaticVirtualFile> {
586
- match path {
587
- "/virtual/file.txt" => Some(StaticVirtualFile {
588
- content: b"hello world\n",
589
- mode: 0o644,
590
- }),
591
- "/virtual/data.bin" => Some(StaticVirtualFile {
592
- content: b"\x00\x01\x02\x03",
593
- mode: 0o644,
594
- }),
595
- "/virtual/empty.txt" => Some(StaticVirtualFile {
596
- content: b"",
597
- mode: 0o644,
598
- }),
599
- "/virtual/subdir/nested.txt" => Some(StaticVirtualFile {
600
- content: b"nested content",
601
- mode: 0o644,
602
- }),
603
- "/virtual/subdir/deep/file.txt" => Some(StaticVirtualFile {
604
- content: b"deep",
605
- mode: 0o644,
606
- }),
607
- "/virtual/readonly.txt" => Some(StaticVirtualFile {
608
- content: b"readonly",
609
- mode: 0o444,
610
- }),
611
- _ => None,
612
- }
613
- }
614
-
615
- /// Gets a virtual file, checking the mutable layer first, then falling back to static.
616
- fn get_virtual_file(path: &str) -> Option<VirtualFile> {
617
- // Check mutable layer first
618
- let mutable_result = MUTABLE_VFS.with(|vfs| {
619
- let vfs = vfs.borrow();
620
- // Check if deleted
621
- if vfs.deleted_files.contains(path) {
622
- return Some(None);
623
- }
624
- // Check if exists in mutable layer
625
- if let Some((content, mode)) = vfs.files.get(path) {
626
- return Some(Some(VirtualFile {
627
- content: content.clone(),
628
- mode: *mode,
629
- }));
630
- }
631
- None
632
- });
633
-
634
- match mutable_result {
635
- Some(Some(file)) => Some(file),
636
- Some(None) => None, // File was deleted
637
- None => {
638
- // Fall back to static VFS
639
- get_static_virtual_file(path).map(|f| VirtualFile {
640
- content: f.content.to_vec(),
641
- mode: f.mode,
642
- })
643
- }
644
- }
645
- }
646
-
647
- // =============================================================================
648
- // Mutable VFS Layer (Thread-Local Storage for Write Operations)
649
- // =============================================================================
650
-
651
- /// Mutable state for the virtual filesystem, supporting write operations.
652
- ///
653
- /// This layer sits on top of the static VFS and allows tests to create, modify, and
654
- /// delete files and directories. The state is thread-local so tests don't interfere
655
- /// with each other.
656
- #[derive(Default)]
657
- struct MutableVfs {
658
- /// Files created or modified during test execution.
659
- files: HashMap<String, (Vec<u8>, i64)>, // path -> (content, mode)
660
- /// Directories created during test execution.
661
- dirs: HashSet<String>,
662
- /// Files deleted during test execution (shadows static VFS entries).
663
- deleted_files: HashSet<String>,
664
- /// Directories deleted during test execution.
665
- deleted_dirs: HashSet<String>,
666
- }
667
-
668
- thread_local! {
669
- /// Thread-local mutable VFS state.
670
- static MUTABLE_VFS: RefCell<MutableVfs> = RefCell::new(MutableVfs::default());
671
- }
672
-
673
- /// Resets the mutable VFS state for a new test.
674
- fn reset_mutable_vfs() {
675
- MUTABLE_VFS.with(|vfs| {
676
- *vfs.borrow_mut() = MutableVfs::default();
677
- });
678
- }
679
-
680
- /// Check if the given path is a directory in the virtual filesystem.
681
- fn is_virtual_dir(path: &str) -> bool {
682
- // Check mutable layer first
683
- let result = MUTABLE_VFS.with(|vfs| {
684
- let vfs = vfs.borrow();
685
- if vfs.deleted_dirs.contains(path) {
686
- return Some(false);
687
- }
688
- if vfs.dirs.contains(path) {
689
- return Some(true);
690
- }
691
- None
692
- });
693
- if let Some(is_dir) = result {
694
- return is_dir;
695
- }
696
- // Fall back to static VFS
697
- matches!(path, "/virtual" | "/virtual/subdir" | "/virtual/subdir/deep")
698
- }
699
-
700
- /// Get directory entries for a virtual directory.
701
- fn get_virtual_dir_entries(path: &str) -> Option<Vec<String>> {
702
- // First check if the directory exists
703
- if !is_virtual_dir(path) {
704
- return None;
705
- }
706
-
707
- // Get static entries (if any)
708
- let static_entries: Vec<&'static str> = match path {
709
- "/virtual" => vec![
710
- "/virtual/file.txt",
711
- "/virtual/data.bin",
712
- "/virtual/empty.txt",
713
- "/virtual/subdir",
714
- "/virtual/readonly.txt",
715
- ],
716
- "/virtual/subdir" => vec!["/virtual/subdir/nested.txt", "/virtual/subdir/deep"],
717
- "/virtual/subdir/deep" => vec!["/virtual/subdir/deep/file.txt"],
718
- _ => vec![],
719
- };
720
-
721
- // Combine with mutable layer
722
- MUTABLE_VFS.with(|vfs| {
723
- let vfs = vfs.borrow();
724
- let mut entries: HashSet<String> = static_entries
725
- .iter()
726
- .filter(|e| {
727
- let s: &str = e;
728
- !vfs.deleted_files.contains(s) && !vfs.deleted_dirs.contains(s)
729
- })
730
- .map(|e| (*e).to_owned())
731
- .collect();
732
-
733
- // Add mutable files and dirs in this directory
734
- let prefix = if path.ends_with('/') {
735
- path.to_owned()
736
- } else {
737
- format!("{path}/")
738
- };
739
- for file_path in vfs.files.keys() {
740
- if file_path.starts_with(&prefix) {
741
- // Only include direct children (not nested)
742
- let rest = &file_path[prefix.len()..];
743
- if !rest.contains('/') {
744
- entries.insert(file_path.clone());
745
- }
746
- }
747
- }
748
- for dir_path in &vfs.dirs {
749
- if dir_path.starts_with(&prefix) {
750
- let rest = &dir_path[prefix.len()..];
751
- if !rest.contains('/') {
752
- entries.insert(dir_path.clone());
753
- }
754
- }
755
- }
756
-
757
- Some(entries.into_iter().collect())
758
- })
759
- }
760
-
761
- /// Helper to get a boolean kwarg by name.
762
- fn get_kwarg_bool(kwargs: &[(MontyObject, MontyObject)], name: &str) -> bool {
763
- for (key, value) in kwargs {
764
- if let MontyObject::String(key_str) = key
765
- && key_str == name
766
- {
767
- return matches!(value, MontyObject::Bool(true));
768
- }
769
- }
770
- false
771
- }
772
-
773
- /// Dispatches an OS function call using the virtual filesystem.
774
- ///
775
- /// Returns an `ExternalResult` to pass back to the Monty interpreter.
776
- /// Raises `FileNotFoundError` for missing files/directories.
777
- #[expect(clippy::cast_possible_wrap)] // Virtual file sizes are tiny, no wrap possible
778
- fn dispatch_os_call(
779
- function: OsFunction,
780
- args: &[MontyObject],
781
- kwargs: &[(MontyObject, MontyObject)],
782
- ) -> ExtFunctionResult {
783
- // Handle GetEnviron first as it takes no path argument
784
- if function == OsFunction::GetEnviron {
785
- // Return the virtual environment as a dict
786
- let env_dict = vec![
787
- (
788
- MontyObject::String("VIRTUAL_HOME".to_owned()),
789
- MontyObject::String("/virtual/home".to_owned()),
790
- ),
791
- (
792
- MontyObject::String("VIRTUAL_USER".to_owned()),
793
- MontyObject::String("testuser".to_owned()),
794
- ),
795
- (
796
- MontyObject::String("VIRTUAL_EMPTY".to_owned()),
797
- MontyObject::String(String::new()),
798
- ),
799
- ];
800
- return MontyObject::Dict(env_dict.into()).into();
801
- }
802
-
803
- // Extract path from MontyObject::Path (or String for backwards compatibility)
804
- let path = match &args[0] {
805
- MontyObject::Path(p) => p.clone(),
806
- MontyObject::String(s) => s.clone(),
807
- other => panic!("OS call: first arg must be path, got {other:?}"),
808
- };
809
-
810
- match function {
811
- OsFunction::GetEnviron => unreachable!("handled above"),
812
- OsFunction::Exists => {
813
- let exists = get_virtual_file(&path).is_some() || is_virtual_dir(&path);
814
- MontyObject::Bool(exists).into()
815
- }
816
- OsFunction::IsFile => {
817
- let is_file = get_virtual_file(&path).is_some();
818
- MontyObject::Bool(is_file).into()
819
- }
820
- OsFunction::IsDir => {
821
- let is_dir = is_virtual_dir(&path);
822
- MontyObject::Bool(is_dir).into()
823
- }
824
- OsFunction::IsSymlink => {
825
- // Virtual filesystem doesn't have symlinks
826
- MontyObject::Bool(false).into()
827
- }
828
- OsFunction::ReadText => {
829
- if let Some(file) = get_virtual_file(&path) {
830
- match std::str::from_utf8(&file.content) {
831
- Ok(text) => MontyObject::String(text.to_owned()).into(),
832
- Err(_) => MontyException::new(
833
- ExcType::UnicodeDecodeError,
834
- Some("'utf-8' codec can't decode bytes".to_owned()),
835
- )
836
- .into(),
837
- }
838
- } else {
839
- MontyException::new(
840
- ExcType::FileNotFoundError,
841
- Some(format!("[Errno 2] No such file or directory: '{path}'")),
842
- )
843
- .into()
844
- }
845
- }
846
- OsFunction::ReadBytes => {
847
- if let Some(file) = get_virtual_file(&path) {
848
- MontyObject::Bytes(file.content).into()
849
- } else {
850
- MontyException::new(
851
- ExcType::FileNotFoundError,
852
- Some(format!("[Errno 2] No such file or directory: '{path}'")),
853
- )
854
- .into()
855
- }
856
- }
857
- OsFunction::Stat => {
858
- if let Some(file) = get_virtual_file(&path) {
859
- file_stat(file.mode, file.content.len() as i64, VFS_MTIME).into()
860
- } else if is_virtual_dir(&path) {
861
- dir_stat(0o755, VFS_MTIME).into()
862
- } else {
863
- MontyException::new(
864
- ExcType::FileNotFoundError,
865
- Some(format!("[Errno 2] No such file or directory: '{path}'")),
866
- )
867
- .into()
868
- }
869
- }
870
- OsFunction::Iterdir => {
871
- if let Some(entries) = get_virtual_dir_entries(&path) {
872
- // Return Path objects, not strings
873
- let list: Vec<MontyObject> = entries.into_iter().map(MontyObject::Path).collect();
874
- MontyObject::List(list).into()
875
- } else {
876
- MontyException::new(
877
- ExcType::FileNotFoundError,
878
- Some(format!("[Errno 2] No such file or directory: '{path}'")),
879
- )
880
- .into()
881
- }
882
- }
883
- OsFunction::Resolve | OsFunction::Absolute => {
884
- // For virtual paths, return as-is (they're already absolute)
885
- MontyObject::String(path).into()
886
- }
887
- OsFunction::Getenv => {
888
- // Virtual environment for testing os.getenv()
889
- // args[0] is key, args[1] is default (may be None)
890
- let key = String::try_from(&args[0]).expect("getenv: first arg must be key string");
891
- let default = &args[1];
892
-
893
- // Provide a few test environment variables
894
- let value = match key.as_str() {
895
- "VIRTUAL_HOME" => Some("/virtual/home"),
896
- "VIRTUAL_USER" => Some("testuser"),
897
- "VIRTUAL_EMPTY" => Some(""),
898
- _ => None,
899
- };
900
-
901
- if let Some(v) = value {
902
- MontyObject::String(v.to_owned()).into()
903
- } else if matches!(default, MontyObject::None) {
904
- MontyObject::None.into()
905
- } else {
906
- // Return the default value
907
- default.clone().into()
908
- }
909
- }
910
- OsFunction::WriteText => {
911
- // args[0] is path, args[1] is text content
912
- let text = String::try_from(&args[1]).expect("write_text: second arg must be string");
913
- MUTABLE_VFS.with(|vfs| {
914
- let mut vfs = vfs.borrow_mut();
915
- vfs.files.insert(path.clone(), (text.into_bytes(), 0o644));
916
- vfs.deleted_files.remove(&path);
917
- });
918
- // write_text returns the number of bytes written
919
- let byte_count = MUTABLE_VFS.with(|vfs| vfs.borrow().files.get(&path).map_or(0, |(c, _)| c.len()));
920
- MontyObject::Int(byte_count as i64).into()
921
- }
922
- OsFunction::WriteBytes => {
923
- // args[0] is path, args[1] is bytes content
924
- let bytes = match &args[1] {
925
- MontyObject::Bytes(b) => b.clone(),
926
- other => panic!("write_bytes: second arg must be bytes, got {other:?}"),
927
- };
928
- let byte_count = bytes.len();
929
- MUTABLE_VFS.with(|vfs| {
930
- let mut vfs = vfs.borrow_mut();
931
- vfs.files.insert(path.clone(), (bytes, 0o644));
932
- vfs.deleted_files.remove(&path);
933
- });
934
- // write_bytes returns the number of bytes written
935
- MontyObject::Int(byte_count as i64).into()
936
- }
937
- OsFunction::Mkdir => {
938
- // Check for parents and exist_ok in kwargs (e.g., mkdir(parents=True, exist_ok=True))
939
- let parents = get_kwarg_bool(kwargs, "parents");
940
- let exist_ok = get_kwarg_bool(kwargs, "exist_ok");
941
-
942
- // Check if already exists
943
- if is_virtual_dir(&path) {
944
- if exist_ok {
945
- return MontyObject::None.into();
946
- }
947
- return MontyException::new(ExcType::OSError, Some(format!("[Errno 17] File exists: '{path}'"))).into();
948
- }
949
-
950
- // Check parent directory
951
- let parent = std::path::Path::new(&path)
952
- .parent()
953
- .map(|p| p.to_string_lossy().to_string())
954
- .unwrap_or_default();
955
- if !parent.is_empty() && !is_virtual_dir(&parent) {
956
- if parents {
957
- // Create parent directories recursively
958
- create_parent_dirs(&parent);
959
- } else {
960
- return MontyException::new(
961
- ExcType::FileNotFoundError,
962
- Some(format!("[Errno 2] No such file or directory: '{path}'")),
963
- )
964
- .into();
965
- }
966
- }
967
-
968
- MUTABLE_VFS.with(|vfs| {
969
- let mut vfs = vfs.borrow_mut();
970
- vfs.deleted_dirs.remove(&path);
971
- vfs.dirs.insert(path);
972
- });
973
- MontyObject::None.into()
974
- }
975
- OsFunction::Unlink => {
976
- // args[0] is path
977
- if get_virtual_file(&path).is_some() {
978
- MUTABLE_VFS.with(|vfs| {
979
- let mut vfs = vfs.borrow_mut();
980
- vfs.files.remove(&path);
981
- vfs.deleted_files.insert(path);
982
- });
983
- MontyObject::None.into()
984
- } else {
985
- MontyException::new(
986
- ExcType::FileNotFoundError,
987
- Some(format!("[Errno 2] No such file or directory: '{path}'")),
988
- )
989
- .into()
990
- }
991
- }
992
- OsFunction::Rmdir => {
993
- // args[0] is path
994
- if is_virtual_dir(&path) {
995
- MUTABLE_VFS.with(|vfs| {
996
- let mut vfs = vfs.borrow_mut();
997
- vfs.dirs.remove(&path);
998
- vfs.deleted_dirs.insert(path);
999
- });
1000
- MontyObject::None.into()
1001
- } else {
1002
- MontyException::new(
1003
- ExcType::FileNotFoundError,
1004
- Some(format!("[Errno 2] No such file or directory: '{path}'")),
1005
- )
1006
- .into()
1007
- }
1008
- }
1009
- OsFunction::Rename => {
1010
- // args[0] is src path, args[1] is dest path
1011
- let dest = match &args[1] {
1012
- MontyObject::Path(p) => p.clone(),
1013
- MontyObject::String(s) => s.clone(),
1014
- other => panic!("rename: second arg must be path, got {other:?}"),
1015
- };
1016
-
1017
- if let Some(file) = get_virtual_file(&path) {
1018
- MUTABLE_VFS.with(|vfs| {
1019
- let mut vfs = vfs.borrow_mut();
1020
- // Remove from old location
1021
- vfs.files.remove(&path);
1022
- vfs.deleted_files.insert(path);
1023
- // Add to new location
1024
- vfs.files.insert(dest, (file.content, file.mode));
1025
- });
1026
- MontyObject::None.into()
1027
- } else if is_virtual_dir(&path) {
1028
- MUTABLE_VFS.with(|vfs| {
1029
- let mut vfs = vfs.borrow_mut();
1030
- vfs.dirs.remove(&path);
1031
- vfs.deleted_dirs.insert(path);
1032
- vfs.dirs.insert(dest);
1033
- });
1034
- MontyObject::None.into()
1035
- } else {
1036
- MontyException::new(
1037
- ExcType::FileNotFoundError,
1038
- Some(format!("[Errno 2] No such file or directory: '{path}'")),
1039
- )
1040
- .into()
1041
- }
1042
- }
1043
- }
1044
- }
1045
-
1046
- /// Helper to create parent directories recursively.
1047
- fn create_parent_dirs(path: &str) {
1048
- if is_virtual_dir(path) {
1049
- return;
1050
- }
1051
- // Create parent first
1052
- if let Some(parent) = std::path::Path::new(path).parent() {
1053
- let parent_str = parent.to_string_lossy().to_string();
1054
- if !parent_str.is_empty() {
1055
- create_parent_dirs(&parent_str);
1056
- }
1057
- }
1058
- // Create this directory
1059
- MUTABLE_VFS.with(|vfs| {
1060
- let mut vfs = vfs.borrow_mut();
1061
- vfs.dirs.insert(path.to_owned());
1062
- });
1063
- }
1064
-
1065
- /// Represents a test failure with details about expected vs actual values.
1066
- #[derive(Debug)]
1067
- struct TestFailure {
1068
- test_name: String,
1069
- kind: String,
1070
- expected: String,
1071
- actual: String,
1072
- }
1073
-
1074
- impl std::fmt::Display for TestFailure {
1075
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1076
- writeln!(
1077
- f,
1078
- "[{}] {} mismatch\ngot {:?}\ndiff:",
1079
- self.test_name, self.kind, self.actual
1080
- )?;
1081
-
1082
- for change in TextDiff::from_lines(&self.expected, &self.actual).iter_all_changes() {
1083
- write!(f, "{}{}", change.tag(), change)?;
1084
- }
1085
- Ok(())
1086
- }
1087
- }
1088
-
1089
- /// Try to run a test, returning Ok(()) on success or Err with failure details.
1090
- ///
1091
- /// This function executes Python code via the MontyRun and validates the result
1092
- /// against the expected outcome specified in the fixture.
1093
- fn try_run_test(path: &Path, code: &str, expectation: &Expectation) -> Result<(), TestFailure> {
1094
- let test_name = path.strip_prefix("test_cases/").unwrap_or(path).display().to_string();
1095
-
1096
- // Reset the mutable VFS for each test
1097
- reset_mutable_vfs();
1098
-
1099
- // Handle ref-count-return tests separately since they need run_ref_counts()
1100
- #[cfg(feature = "ref-count-return")]
1101
- if let Expectation::RefCounts(expected) = expectation {
1102
- match MontyRun::new(code.to_owned(), &test_name, vec![]) {
1103
- Ok(ex) => {
1104
- let result = ex.run_ref_counts(vec![]);
1105
- match result {
1106
- Ok(monty::RefCountOutput {
1107
- counts,
1108
- unique_refs,
1109
- heap_count,
1110
- ..
1111
- }) => {
1112
- // Strict matching: verify all heap objects are accounted for by variables
1113
- if unique_refs != heap_count {
1114
- return Err(TestFailure {
1115
- test_name,
1116
- kind: "Strict matching".to_string(),
1117
- expected: format!("{heap_count} heap objects"),
1118
- actual: format!("{unique_refs} referenced by variables, counts: {counts:?}"),
1119
- });
1120
- }
1121
- if &counts != expected {
1122
- return Err(TestFailure {
1123
- test_name,
1124
- kind: "ref-counts".to_string(),
1125
- expected: format!("{expected:?}"),
1126
- actual: format!("{counts:?}"),
1127
- });
1128
- }
1129
- return Ok(());
1130
- }
1131
- Err(e) => {
1132
- return Err(TestFailure {
1133
- test_name,
1134
- kind: "Runtime".to_string(),
1135
- expected: "success".to_string(),
1136
- actual: e.to_string(),
1137
- });
1138
- }
1139
- }
1140
- }
1141
- Err(parse_err) => {
1142
- return Err(TestFailure {
1143
- test_name,
1144
- kind: "Parse".to_string(),
1145
- expected: "success".to_string(),
1146
- actual: parse_err.to_string(),
1147
- });
1148
- }
1149
- }
1150
- }
1151
-
1152
- match MontyRun::new(code.to_owned(), &test_name, vec![]) {
1153
- Ok(ex) => {
1154
- let limits = ResourceLimits::new().max_recursion_depth(Some(TEST_RECURSION_LIMIT));
1155
- let result = ex.run(vec![], LimitedTracker::new(limits), PrintWriter::Stdout);
1156
- match result {
1157
- Ok(obj) => match expectation {
1158
- Expectation::ReturnStr(expected) => {
1159
- let output = obj.to_string();
1160
- if output != *expected {
1161
- return Err(TestFailure {
1162
- test_name,
1163
- kind: "str()".to_string(),
1164
- expected: expected.clone(),
1165
- actual: output,
1166
- });
1167
- }
1168
- }
1169
- Expectation::Return(expected) => {
1170
- let output = obj.py_repr();
1171
- if output != *expected {
1172
- return Err(TestFailure {
1173
- test_name,
1174
- kind: "py_repr()".to_string(),
1175
- expected: expected.clone(),
1176
- actual: output,
1177
- });
1178
- }
1179
- }
1180
- Expectation::ReturnType(expected) => {
1181
- let output = obj.type_name();
1182
- if output != expected {
1183
- return Err(TestFailure {
1184
- test_name,
1185
- kind: "type_name()".to_string(),
1186
- expected: expected.clone(),
1187
- actual: output.to_string(),
1188
- });
1189
- }
1190
- }
1191
- #[cfg(not(feature = "ref-count-return"))]
1192
- Expectation::RefCounts(_) => {
1193
- // Skip ref-count tests when feature is disabled
1194
- }
1195
- Expectation::NoException => {
1196
- // Success - code ran without exception as expected
1197
- }
1198
- Expectation::Raise(expected) | Expectation::Traceback(expected) => {
1199
- return Err(TestFailure {
1200
- test_name,
1201
- kind: "Exception".to_string(),
1202
- expected: expected.clone(),
1203
- actual: "no exception raised".to_string(),
1204
- });
1205
- }
1206
- #[cfg(feature = "ref-count-return")]
1207
- Expectation::RefCounts(_) => unreachable!(),
1208
- },
1209
- Err(e) => {
1210
- if let Expectation::Raise(expected) = expectation {
1211
- let output = e.py_repr();
1212
- if output != *expected {
1213
- return Err(TestFailure {
1214
- test_name,
1215
- kind: "Exception".to_string(),
1216
- expected: expected.clone(),
1217
- actual: output,
1218
- });
1219
- }
1220
- } else if let Expectation::Traceback(expected) = expectation {
1221
- let output = e.to_string();
1222
- if output != *expected {
1223
- return Err(TestFailure {
1224
- test_name,
1225
- kind: "Traceback".to_string(),
1226
- expected: expected.clone(),
1227
- actual: output,
1228
- });
1229
- }
1230
- } else {
1231
- return Err(TestFailure {
1232
- test_name,
1233
- kind: "Unexpected error".to_string(),
1234
- expected: "success".to_string(),
1235
- actual: e.to_string(),
1236
- });
1237
- }
1238
- }
1239
- }
1240
- }
1241
- Err(parse_err) => {
1242
- if let Expectation::Raise(expected) = expectation {
1243
- let output = parse_err.py_repr();
1244
- if output != *expected {
1245
- return Err(TestFailure {
1246
- test_name,
1247
- kind: "Parse error".to_string(),
1248
- expected: expected.clone(),
1249
- actual: output,
1250
- });
1251
- }
1252
- } else if let Expectation::Traceback(expected) = expectation {
1253
- let output = parse_err.to_string();
1254
- if output != *expected {
1255
- return Err(TestFailure {
1256
- test_name,
1257
- kind: "Traceback".to_string(),
1258
- expected: expected.clone(),
1259
- actual: output,
1260
- });
1261
- }
1262
- } else {
1263
- return Err(TestFailure {
1264
- test_name,
1265
- kind: "Unexpected parse error".to_string(),
1266
- expected: "success".to_string(),
1267
- actual: parse_err.to_string(),
1268
- });
1269
- }
1270
- }
1271
- }
1272
- Ok(())
1273
- }
1274
-
1275
- /// Try to run a test using MontyRun with external function support.
1276
- ///
1277
- /// This function handles tests marked with `# call-external` directive by using the
1278
- /// iterative executor API and providing implementations for predefined external functions.
1279
- fn try_run_iter_test(path: &Path, code: &str, expectation: &Expectation) -> Result<(), TestFailure> {
1280
- let test_name = path.strip_prefix("test_cases/").unwrap_or(path).display().to_string();
1281
-
1282
- // Reset the mutable VFS for each test
1283
- reset_mutable_vfs();
1284
-
1285
- // Ref-counting tests not supported in iter mode
1286
- #[cfg(feature = "ref-count-return")]
1287
- if matches!(expectation, Expectation::RefCounts(_)) {
1288
- return Err(TestFailure {
1289
- test_name,
1290
- kind: "Configuration".to_string(),
1291
- expected: "non-refcount test".to_string(),
1292
- actual: "ref-counts tests are not supported in iter mode".to_string(),
1293
- });
1294
- }
1295
-
1296
- let exec = match MontyRun::new(code.to_owned(), &test_name, vec![]) {
1297
- Ok(e) => e,
1298
- Err(parse_err) => {
1299
- if let Expectation::Raise(expected) = expectation {
1300
- let output = parse_err.py_repr();
1301
- if output != *expected {
1302
- return Err(TestFailure {
1303
- test_name,
1304
- kind: "Parse error".to_string(),
1305
- expected: expected.clone(),
1306
- actual: output,
1307
- });
1308
- }
1309
- return Ok(());
1310
- } else if let Expectation::Traceback(expected) = expectation {
1311
- let output = parse_err.to_string();
1312
- if output != *expected {
1313
- return Err(TestFailure {
1314
- test_name,
1315
- kind: "Traceback".to_string(),
1316
- expected: expected.clone(),
1317
- actual: output,
1318
- });
1319
- }
1320
- return Ok(());
1321
- }
1322
- return Err(TestFailure {
1323
- test_name,
1324
- kind: "Unexpected parse error".to_string(),
1325
- expected: "success".to_string(),
1326
- actual: parse_err.to_string(),
1327
- });
1328
- }
1329
- };
1330
-
1331
- // Run execution loop, handling external function calls until complete
1332
- let result = run_iter_loop(exec);
1333
-
1334
- match result {
1335
- Ok(obj) => match expectation {
1336
- Expectation::ReturnStr(expected) => {
1337
- let output = obj.to_string();
1338
- if output != *expected {
1339
- return Err(TestFailure {
1340
- test_name,
1341
- kind: "str()".to_string(),
1342
- expected: expected.clone(),
1343
- actual: output,
1344
- });
1345
- }
1346
- }
1347
- Expectation::Return(expected) => {
1348
- let output = obj.py_repr();
1349
- if output != *expected {
1350
- return Err(TestFailure {
1351
- test_name,
1352
- kind: "py_repr()".to_string(),
1353
- expected: expected.clone(),
1354
- actual: output,
1355
- });
1356
- }
1357
- }
1358
- Expectation::ReturnType(expected) => {
1359
- let output = obj.type_name();
1360
- if output != expected {
1361
- return Err(TestFailure {
1362
- test_name,
1363
- kind: "type_name()".to_string(),
1364
- expected: expected.clone(),
1365
- actual: output.to_string(),
1366
- });
1367
- }
1368
- }
1369
- #[cfg(not(feature = "ref-count-return"))]
1370
- Expectation::RefCounts(_) => {}
1371
- Expectation::NoException => {}
1372
- Expectation::Raise(expected) | Expectation::Traceback(expected) => {
1373
- return Err(TestFailure {
1374
- test_name,
1375
- kind: "Exception".to_string(),
1376
- expected: expected.clone(),
1377
- actual: "no exception raised".to_string(),
1378
- });
1379
- }
1380
- #[cfg(feature = "ref-count-return")]
1381
- Expectation::RefCounts(_) => unreachable!(),
1382
- },
1383
- Err(e) => {
1384
- if let Expectation::Raise(expected) = expectation {
1385
- let output = e.py_repr();
1386
- if output != *expected {
1387
- return Err(TestFailure {
1388
- test_name,
1389
- kind: "Exception".to_string(),
1390
- expected: expected.clone(),
1391
- actual: output,
1392
- });
1393
- }
1394
- } else if let Expectation::Traceback(expected) = expectation {
1395
- let output = e.to_string();
1396
- if output != *expected {
1397
- return Err(TestFailure {
1398
- test_name,
1399
- kind: "Traceback".to_string(),
1400
- expected: expected.clone(),
1401
- actual: output,
1402
- });
1403
- }
1404
- } else {
1405
- return Err(TestFailure {
1406
- test_name,
1407
- kind: "Unexpected error".to_string(),
1408
- expected: "success".to_string(),
1409
- actual: e.to_string(),
1410
- });
1411
- }
1412
- }
1413
- }
1414
- Ok(())
1415
- }
1416
-
1417
- /// Execute the iter loop, dispatching external function calls until complete.
1418
- ///
1419
- /// When `ref-count-panic` feature is NOT enabled, this function also tests
1420
- /// serialization round-trips by dumping and loading the execution state at
1421
- /// each external function call boundary.
1422
- ///
1423
- /// Supports both synchronous and asynchronous external functions:
1424
- /// - Sync functions: result is passed immediately via `state.run()`
1425
- /// - Async functions: `state.run_pending()` creates a future, resolved via `ResolveFutures`
1426
- fn run_iter_loop(exec: MontyRun) -> Result<MontyObject, MontyException> {
1427
- let limits = ResourceLimits::new().max_recursion_depth(Some(TEST_RECURSION_LIMIT));
1428
- let mut progress = exec.start(vec![], LimitedTracker::new(limits), PrintWriter::Stdout)?;
1429
-
1430
- // Track pending async calls: (call_id, result_value)
1431
- let mut pending_results: Vec<(u32, MontyObject)> = Vec::new();
1432
-
1433
- loop {
1434
- // Test serialization round-trip at each step (skip when ref-count-panic is enabled
1435
- // since the old RunProgress would panic on drop without proper cleanup)
1436
- #[cfg(not(feature = "ref-count-panic"))]
1437
- {
1438
- let bytes = progress.dump().expect("failed to dump RunProgress");
1439
- progress = RunProgress::load(&bytes).expect("failed to load RunProgress");
1440
- }
1441
-
1442
- match progress {
1443
- RunProgress::Complete(result) => return Ok(result),
1444
- RunProgress::FunctionCall(call) => {
1445
- // Method calls on dataclasses are dispatched to the host.
1446
- // Dispatch known methods; return AttributeError for unknown ones.
1447
- if call.method_call {
1448
- let result = dispatch_method_call(&call.function_name, &call.args, &call.kwargs);
1449
- progress = call.resume(result, PrintWriter::Stdout)?;
1450
- continue;
1451
- }
1452
- let dispatch_result = dispatch_external_call(&call.function_name, call.args.clone());
1453
- match dispatch_result {
1454
- DispatchResult::Sync(return_value) => {
1455
- progress = call.resume(return_value, PrintWriter::Stdout)?;
1456
- }
1457
- DispatchResult::Async(result_value) => {
1458
- // Store the result for later resolution
1459
- pending_results.push((call.call_id, result_value));
1460
- // Continue execution with a pending future
1461
- progress = call.resume_pending(PrintWriter::Stdout)?;
1462
- }
1463
- }
1464
- }
1465
- RunProgress::ResolveFutures(state) => {
1466
- // Resolve all pending futures that we have results for
1467
- let results: Vec<(u32, ExtFunctionResult)> = state
1468
- .pending_call_ids()
1469
- .iter()
1470
- .filter_map(|p| {
1471
- pending_results.iter().position(|(id, _)| id == p).map(|idx| {
1472
- let (call_id, value) = pending_results.remove(idx);
1473
- (call_id, ExtFunctionResult::Return(value))
1474
- })
1475
- })
1476
- .collect();
1477
-
1478
- assert!(
1479
- !results.is_empty(),
1480
- "ResolveFutures: no results available for pending calls: {:?}",
1481
- state.pending_call_ids().iter().collect::<Vec<_>>()
1482
- );
1483
-
1484
- progress = state.resume(results, PrintWriter::Stdout)?;
1485
- }
1486
- RunProgress::NameLookup(lookup) => {
1487
- let result = match lookup.name.as_str() {
1488
- // External functions — resolved as callable Function objects
1489
- "add_ints" | "concat_strings" | "return_value" | "get_list" | "raise_error" | "make_point"
1490
- | "make_mutable_point" | "make_user" | "make_empty" | "async_call" => {
1491
- NameLookupResult::Value(MontyObject::Function {
1492
- name: lookup.name.clone(),
1493
- docstring: None,
1494
- })
1495
- }
1496
- // Non-function constants — resolved as plain values
1497
- "CONST_INT" => NameLookupResult::Value(MontyObject::Int(42)),
1498
- "CONST_STR" => NameLookupResult::Value(MontyObject::String("hello".to_string())),
1499
- #[expect(clippy::approx_constant, reason = "3.14 is the intended test value")]
1500
- "CONST_FLOAT" => NameLookupResult::Value(MontyObject::Float(3.14)),
1501
- "CONST_BOOL" => NameLookupResult::Value(MontyObject::Bool(true)),
1502
- "CONST_LIST" => NameLookupResult::Value(MontyObject::List(vec![
1503
- MontyObject::Int(1),
1504
- MontyObject::Int(2),
1505
- MontyObject::Int(3),
1506
- ])),
1507
- "CONST_NONE" => NameLookupResult::Value(MontyObject::None),
1508
- // Unknown names → NameError
1509
- _ => NameLookupResult::Undefined,
1510
- };
1511
- progress = lookup.resume(result, PrintWriter::Stdout)?;
1512
- }
1513
- RunProgress::OsCall(call) => {
1514
- let result = dispatch_os_call(call.function, &call.args, &call.kwargs);
1515
- progress = call.resume(result, PrintWriter::Stdout)?;
1516
- }
1517
- }
1518
- }
1519
- }
1520
-
1521
- /// Split Python code into statements and a final expression to evaluate.
1522
- ///
1523
- /// For Return expectations, the last non-empty line is the expression to evaluate.
1524
- /// For Raise/NoException, the entire code is statements (returns None for expression).
1525
- ///
1526
- /// Returns (statements_code, optional_final_expression).
1527
- fn split_code_for_module(code: &str, need_return_value: bool) -> (String, Option<String>) {
1528
- let lines: Vec<&str> = code.lines().collect();
1529
-
1530
- // Find the last non-empty line
1531
- let last_idx = lines
1532
- .iter()
1533
- .rposition(|line| !line.trim().is_empty())
1534
- .expect("Empty code");
1535
-
1536
- if need_return_value {
1537
- let last_line = lines[last_idx].trim();
1538
-
1539
- // Check if the last line is a statement (can't be evaluated as an expression)
1540
- // Matches both `assert expr` and `assert(expr)` forms
1541
- if last_line.starts_with("assert ") || last_line.starts_with("assert(") {
1542
- // All code is statements, no expression to evaluate
1543
- (lines[..=last_idx].join("\n"), None)
1544
- } else {
1545
- // Everything except last line is statements, last line is the expression
1546
- let statements = lines[..last_idx].join("\n");
1547
- let expr = last_line.to_string();
1548
- (statements, Some(expr))
1549
- }
1550
- } else {
1551
- // All code is statements (for exception tests or NoException)
1552
- (lines[..=last_idx].join("\n"), None)
1553
- }
1554
- }
1555
-
1556
- /// Wraps code in an async context for CPython execution.
1557
- ///
1558
- /// Monty supports top-level `await`, but CPython does not. This function transforms code
1559
- /// like:
1560
- ///
1561
- /// ```python
1562
- /// async def foo():
1563
- /// return 1
1564
- /// result = await foo()
1565
- /// ```
1566
- ///
1567
- /// Into:
1568
- ///
1569
- /// ```python
1570
- /// import asyncio
1571
- /// async def __test_main():
1572
- /// async def foo():
1573
- /// return 1
1574
- /// result = await foo()
1575
- /// return result # if need_return_value
1576
- /// __test_result__ = asyncio.run(__test_main())
1577
- /// ```
1578
- fn wrap_code_for_async(code: &str, need_return_value: bool) -> (String, Option<String>) {
1579
- let lines: Vec<&str> = code.lines().collect();
1580
-
1581
- // Find the last non-empty, non-comment line
1582
- let last_idx = lines
1583
- .iter()
1584
- .rposition(|line| {
1585
- let trimmed = line.trim();
1586
- !trimmed.is_empty() && !trimmed.starts_with('#')
1587
- })
1588
- .expect("Empty code");
1589
-
1590
- // Indent all code by 4 spaces for the function body
1591
- let indented: String = lines
1592
- .iter()
1593
- .map(|line| {
1594
- if line.is_empty() {
1595
- String::new()
1596
- } else {
1597
- format!(" {line}")
1598
- }
1599
- })
1600
- .collect::<Vec<_>>()
1601
- .join("\n");
1602
-
1603
- let return_stmt = if need_return_value {
1604
- // The last non-empty, non-comment line is the expression to return
1605
- let last_line = lines[last_idx].trim();
1606
- format!("\n return {last_line}")
1607
- } else {
1608
- String::new()
1609
- };
1610
-
1611
- let wrapped = format!(
1612
- "import asyncio\nasync def __test_main():\n{indented}{return_stmt}\n__test_result__ = asyncio.run(__test_main())"
1613
- );
1614
-
1615
- if need_return_value {
1616
- (wrapped, Some("__test_result__".to_string()))
1617
- } else {
1618
- (wrapped, None)
1619
- }
1620
- }
1621
-
1622
- /// Run the traceback script to get CPython's traceback output for a test file.
1623
- ///
1624
- /// This imports scripts/run_traceback.py via pyo3 and calls `run_file_and_get_traceback()`
1625
- /// which executes the file via runpy.run_path() to ensure full traceback information
1626
- /// (including caret lines) is preserved.
1627
- ///
1628
- /// When `iter_mode` is true, external function implementations are injected into the
1629
- /// file's globals before execution.
1630
- ///
1631
- /// When `async_mode` is true, code is wrapped in an async context before execution.
1632
- fn run_traceback_script(path: &Path, iter_mode: bool, async_mode: bool) -> String {
1633
- Python::attach(|py| {
1634
- let run_traceback = import_run_traceback(py);
1635
-
1636
- // Get absolute path for the test file
1637
- let abs_path = path.canonicalize().expect("Failed to get absolute path");
1638
- let path_str = abs_path.to_str().expect("Invalid UTF-8 in path");
1639
-
1640
- // Call run_file_and_get_traceback with the recursion limit, iter_mode, and async_mode flags
1641
- let result = run_traceback
1642
- .call_method1(
1643
- "run_file_and_get_traceback",
1644
- (path_str, TEST_RECURSION_LIMIT, iter_mode, async_mode),
1645
- )
1646
- .expect("Failed to call run_file_and_get_traceback");
1647
-
1648
- // Handle None return (no exception raised)
1649
- if result.is_none() {
1650
- String::new()
1651
- } else {
1652
- result
1653
- .extract()
1654
- .expect("Failed to extract string from return value of run_file_and_get_traceback")
1655
- }
1656
- })
1657
- }
1658
-
1659
- fn format_traceback(py: Python<'_>, exc: &PyErr) -> String {
1660
- let run_traceback = import_run_traceback(py);
1661
- let exc_value = exc.value(py);
1662
- let return_value = run_traceback
1663
- .call_method1("format_full_traceback", (exc_value,))
1664
- .expect("Failed to call format_full_traceback");
1665
- return_value
1666
- .extract()
1667
- .expect("failed to extract string from return value of format_full_traceback")
1668
- }
1669
-
1670
- /// Import the run_traceback module
1671
- fn import_run_traceback(py: Python<'_>) -> Bound<'_, PyModule> {
1672
- // Add scripts directory to sys.path (tests run from crates/monty/)
1673
- let sys = py.import("sys").expect("Failed to import sys");
1674
- let sys_path = sys.getattr("path").expect("Failed to get sys.path");
1675
- sys_path
1676
- .call_method1("insert", (0, "../../scripts"))
1677
- .expect("Failed to add scripts to sys.path");
1678
-
1679
- // Import the run_traceback module
1680
- py.import("run_traceback").expect("Failed to import run_traceback")
1681
- }
1682
-
1683
- /// Result from CPython execution - either a value to compare, or an early return.
1684
- enum CpythonResult {
1685
- /// Value to compare against expectation
1686
- Value(String),
1687
- /// No value to compare (NoException test succeeded)
1688
- NoValue,
1689
- /// Test failed with this error
1690
- Failed(TestFailure),
1691
- }
1692
-
1693
- /// Try to run a test through CPython, returning Ok(()) on success or Err with failure details.
1694
- ///
1695
- /// This function executes the same Python code via CPython (using pyo3) and
1696
- /// compares the result with the expected value. This ensures Monty behaves
1697
- /// identically to CPython.
1698
- ///
1699
- /// Code is executed at module level (not wrapped in a function) so that
1700
- /// `global` keyword semantics work correctly.
1701
- ///
1702
- /// RefCounts tests are skipped as they're Monty-specific.
1703
- /// Traceback tests use scripts/run_traceback.py for reliable caret line support.
1704
- fn try_run_cpython_test(
1705
- path: &Path,
1706
- code: &str,
1707
- expectation: &Expectation,
1708
- iter_mode: bool,
1709
- async_mode: bool,
1710
- ) -> Result<(), TestFailure> {
1711
- // Ensure Python modules are imported before parallel tests access them.
1712
- // This prevents race conditions during module initialization.
1713
- ensure_python_modules_imported();
1714
-
1715
- // Skip RefCounts tests - only relevant for Monty
1716
- if matches!(expectation, Expectation::RefCounts(_)) {
1717
- return Ok(());
1718
- }
1719
-
1720
- let test_name = path.strip_prefix("test_cases/").unwrap_or(path).display().to_string();
1721
-
1722
- // Traceback tests use the external script for reliable caret line support
1723
- if let Expectation::Traceback(expected) = expectation {
1724
- let result = run_traceback_script(path, iter_mode, async_mode);
1725
- if result != *expected {
1726
- return Err(TestFailure {
1727
- test_name,
1728
- kind: "CPython traceback".to_string(),
1729
- expected: expected.clone(),
1730
- actual: result,
1731
- });
1732
- }
1733
- return Ok(());
1734
- }
1735
-
1736
- let need_return_value = matches!(
1737
- expectation,
1738
- Expectation::Return(_) | Expectation::ReturnStr(_) | Expectation::ReturnType(_)
1739
- );
1740
-
1741
- // Use async wrapper for tests with top-level await
1742
- let (statements, maybe_expr) = if async_mode {
1743
- wrap_code_for_async(code, need_return_value)
1744
- } else {
1745
- split_code_for_module(code, need_return_value)
1746
- };
1747
-
1748
- let result: CpythonResult = Python::attach(|py| {
1749
- // Execute statements at module level
1750
- let globals = PyDict::new(py);
1751
-
1752
- // For iter mode tests, inject external function implementations into globals
1753
- if iter_mode {
1754
- let ext_funcs_cstr = CString::new(ITER_EXT_FUNCTIONS_PYTHON).expect("Invalid C string in ext funcs");
1755
- py.run(&ext_funcs_cstr, Some(&globals), None)
1756
- .expect("Failed to define external functions for iter mode");
1757
- }
1758
-
1759
- // Run the statements
1760
- let statements_cstr = CString::new(statements.as_str()).expect("Invalid C string in statements");
1761
- let stmt_result = py.run(&statements_cstr, Some(&globals), None);
1762
-
1763
- // Handle exception during statement execution
1764
- if let Err(e) = stmt_result {
1765
- if matches!(expectation, Expectation::NoException) {
1766
- return CpythonResult::Failed(TestFailure {
1767
- test_name: test_name.clone(),
1768
- kind: "CPython unexpected exception".to_string(),
1769
- expected: "no exception".to_string(),
1770
- actual: format_traceback(py, &e),
1771
- });
1772
- }
1773
- if matches!(expectation, Expectation::Raise(_)) {
1774
- return CpythonResult::Value(format_cpython_exception(py, &e));
1775
- }
1776
- return CpythonResult::Failed(TestFailure {
1777
- test_name: test_name.clone(),
1778
- kind: "CPython unexpected exception".to_string(),
1779
- expected: "success".to_string(),
1780
- actual: format_traceback(py, &e),
1781
- });
1782
- }
1783
-
1784
- // If we have an expression to evaluate, evaluate it
1785
- if let Some(expr) = maybe_expr {
1786
- let expr_cstr = CString::new(expr.as_str()).expect("Invalid C string in expr");
1787
- match py.eval(&expr_cstr, Some(&globals), None) {
1788
- Ok(result) => {
1789
- // Code returned successfully - format based on expectation type
1790
- match expectation {
1791
- Expectation::Return(_) => CpythonResult::Value(result.repr().unwrap().to_string()),
1792
- Expectation::ReturnStr(_) => CpythonResult::Value(result.str().unwrap().to_string()),
1793
- Expectation::ReturnType(_) => {
1794
- CpythonResult::Value(result.get_type().name().unwrap().to_string())
1795
- }
1796
- Expectation::Raise(expected) => CpythonResult::Failed(TestFailure {
1797
- test_name: test_name.clone(),
1798
- kind: "CPython exception".to_string(),
1799
- expected: expected.clone(),
1800
- actual: "no exception raised".to_string(),
1801
- }),
1802
- // Traceback tests are handled by run_traceback_script above
1803
- Expectation::Traceback(_) | Expectation::NoException | Expectation::RefCounts(_) => {
1804
- unreachable!()
1805
- }
1806
- }
1807
- }
1808
- Err(e) => {
1809
- // Expression raised an exception
1810
- if matches!(expectation, Expectation::NoException) {
1811
- return CpythonResult::Failed(TestFailure {
1812
- test_name: test_name.clone(),
1813
- kind: "CPython unexpected exception".to_string(),
1814
- expected: "no exception".to_string(),
1815
- actual: format_traceback(py, &e),
1816
- });
1817
- }
1818
- if matches!(expectation, Expectation::Raise(_)) {
1819
- return CpythonResult::Value(format_cpython_exception(py, &e));
1820
- }
1821
- // Traceback tests are handled by run_traceback_script above
1822
- CpythonResult::Failed(TestFailure {
1823
- test_name: test_name.clone(),
1824
- kind: "CPython unexpected exception".to_string(),
1825
- expected: "success".to_string(),
1826
- actual: format_traceback(py, &e),
1827
- })
1828
- }
1829
- }
1830
- } else {
1831
- // No expression to evaluate
1832
- // Traceback tests are handled by run_traceback_script above
1833
- if let Expectation::Raise(expected) = expectation {
1834
- return CpythonResult::Failed(TestFailure {
1835
- test_name: test_name.clone(),
1836
- kind: "CPython exception".to_string(),
1837
- expected: expected.clone(),
1838
- actual: "no exception raised".to_string(),
1839
- });
1840
- }
1841
- CpythonResult::NoValue // NoException expectation - success
1842
- }
1843
- });
1844
-
1845
- match result {
1846
- CpythonResult::Value(actual) => {
1847
- let expected = expectation.expected_value();
1848
- if actual != expected {
1849
- return Err(TestFailure {
1850
- test_name,
1851
- kind: "CPython result".to_string(),
1852
- expected: expected.to_string(),
1853
- actual,
1854
- });
1855
- }
1856
- Ok(())
1857
- }
1858
- CpythonResult::NoValue => Ok(()),
1859
- CpythonResult::Failed(failure) => Err(failure),
1860
- }
1861
- }
1862
-
1863
- /// Format a CPython exception into the expected format.
1864
- fn format_cpython_exception(py: Python<'_>, e: &PyErr) -> String {
1865
- let exc_type = e.get_type(py).name().unwrap();
1866
- let exc_message: String = e
1867
- .value(py)
1868
- .getattr("args")
1869
- .and_then(|args| args.get_item(0))
1870
- .and_then(|item| item.extract())
1871
- .unwrap_or_default();
1872
-
1873
- if exc_message.is_empty() {
1874
- format!("{exc_type}()")
1875
- } else if exc_message.contains('\'') {
1876
- // Use double quotes when message contains single quotes (like Python's repr)
1877
- format!("{exc_type}(\"{exc_message}\")")
1878
- } else {
1879
- // Use single quotes (default Python repr format)
1880
- format!("{exc_type}('{exc_message}')")
1881
- }
1882
- }
1883
-
1884
- /// Timeout duration for Monty tests.
1885
- ///
1886
- /// Tests that exceed this duration are considered to be hanging (infinite loop)
1887
- /// and will fail with a timeout error.
1888
- const TEST_TIMEOUT: Duration = Duration::from_secs(2);
1889
-
1890
- /// Result from running a test with a timeout.
1891
- enum TimeoutResult<T> {
1892
- /// The closure completed successfully.
1893
- Ok(T),
1894
- /// The closure panicked with the given message.
1895
- Panicked(String),
1896
- /// The timeout was exceeded.
1897
- TimedOut,
1898
- }
1899
-
1900
- /// Runs a closure with a timeout, returning an error if it exceeds the duration or panics.
1901
- ///
1902
- /// Spawns the closure in a separate thread and waits for the result with a timeout.
1903
- /// Distinguishes between three cases:
1904
- /// - Success: the closure returned normally
1905
- /// - Panic: the closure panicked (detected via channel disconnect + catch_unwind)
1906
- /// - Timeout: the timeout was exceeded (possible infinite loop)
1907
- ///
1908
- /// Note that if a timeout occurs, the spawned thread will continue running in the
1909
- /// background (Rust doesn't support killing threads), but the test will fail immediately.
1910
- fn run_with_timeout<F, T>(timeout: Duration, f: F) -> TimeoutResult<T>
1911
- where
1912
- F: FnOnce() -> T + Send + 'static,
1913
- T: Send + 'static,
1914
- {
1915
- let (tx, rx) = mpsc::channel();
1916
- thread::spawn(move || {
1917
- // Catch panics so we can report them properly instead of as timeouts
1918
- let result = panic::catch_unwind(AssertUnwindSafe(f));
1919
- match result {
1920
- Ok(value) => {
1921
- let _ = tx.send(Ok(value));
1922
- }
1923
- Err(panic_payload) => {
1924
- // Extract panic message from the payload
1925
- let msg = if let Some(s) = panic_payload.downcast_ref::<&str>() {
1926
- (*s).to_string()
1927
- } else if let Some(s) = panic_payload.downcast_ref::<String>() {
1928
- s.clone()
1929
- } else {
1930
- "unknown panic".to_string()
1931
- };
1932
- let _ = tx.send(Err(msg));
1933
- }
1934
- }
1935
- });
1936
-
1937
- match rx.recv_timeout(timeout) {
1938
- Ok(Ok(value)) => TimeoutResult::Ok(value),
1939
- Ok(Err(panic_msg)) => TimeoutResult::Panicked(panic_msg),
1940
- Err(RecvTimeoutError::Timeout) => TimeoutResult::TimedOut,
1941
- // Disconnected without sending means something went very wrong
1942
- Err(RecvTimeoutError::Disconnected) => {
1943
- TimeoutResult::Panicked("thread terminated without sending result".to_string())
1944
- }
1945
- }
1946
- }
1947
-
1948
- /// Test function that runs each fixture through Monty.
1949
- ///
1950
- /// Handles xfail with strict semantics: if a test is marked `xfail=monty`, it must fail.
1951
- /// If an xfail test passes unexpectedly, that's an error.
1952
- fn run_test_cases_monty(path: &Path) -> Result<(), Box<dyn Error>> {
1953
- let content = fs::read_to_string(path)?;
1954
- let (code, expectation, config) = parse_fixture(&content);
1955
- let test_name = path.strip_prefix("test_cases/").unwrap_or(path).display().to_string();
1956
-
1957
- // Move data into the closure since it needs 'static lifetime
1958
- let path_owned = path.to_owned();
1959
- let iter_mode = config.iter_mode;
1960
-
1961
- let result = run_with_timeout(TEST_TIMEOUT, move || {
1962
- if iter_mode {
1963
- try_run_iter_test(&path_owned, &code, &expectation)
1964
- } else {
1965
- try_run_test(&path_owned, &code, &expectation)
1966
- }
1967
- });
1968
-
1969
- // Handle timeout/panic errors from the test thread
1970
- let result = match result {
1971
- TimeoutResult::Ok(inner_result) => inner_result,
1972
- TimeoutResult::Panicked(panic_msg) => Err(TestFailure {
1973
- test_name: test_name.clone(),
1974
- kind: "Panic".to_string(),
1975
- expected: "no panic".to_string(),
1976
- actual: format!("test panicked: {panic_msg}"),
1977
- }),
1978
- TimeoutResult::TimedOut => Err(TestFailure {
1979
- test_name: test_name.clone(),
1980
- kind: "Timeout".to_string(),
1981
- expected: format!("completion within {TEST_TIMEOUT:?}"),
1982
- actual: format!("test timed out after {TEST_TIMEOUT:?} (possible infinite loop)"),
1983
- }),
1984
- };
1985
-
1986
- if config.xfail_monty {
1987
- // Strict xfail: test must fail; if it passed, xfail should be removed
1988
- assert!(
1989
- result.is_err(),
1990
- "[{test_name}] Test marked xfail=monty passed unexpectedly. Remove xfail if the test is now fixed."
1991
- );
1992
- } else if let Err(failure) = result {
1993
- panic!("{failure}");
1994
- }
1995
- Ok(())
1996
- }
1997
-
1998
- /// Test function that runs each fixture through CPython.
1999
- ///
2000
- /// Handles xfail with strict semantics: if a test is marked `xfail=cpython`, it must fail.
2001
- /// If an xfail test passes unexpectedly, that's an error.
2002
- fn run_test_cases_cpython(path: &Path) -> Result<(), Box<dyn Error>> {
2003
- let content = fs::read_to_string(path)?;
2004
- let (code, expectation, config) = parse_fixture(&content);
2005
- let test_name = path.strip_prefix("test_cases/").unwrap_or(path).display().to_string();
2006
-
2007
- let result = try_run_cpython_test(path, &code, &expectation, config.iter_mode, config.async_mode);
2008
-
2009
- if config.xfail_cpython {
2010
- // Strict xfail: test must fail; if it passed, xfail should be removed
2011
- assert!(
2012
- result.is_err(),
2013
- "[{test_name}] Test marked xfail=cpython passed unexpectedly. Remove xfail if the test is now fixed."
2014
- );
2015
- } else if let Err(failure) = result {
2016
- panic!("{failure}");
2017
- }
2018
- Ok(())
2019
- }
2020
-
2021
- // Generate tests for all fixture files using datatest-stable harness macro
2022
- datatest_stable::harness!(
2023
- run_test_cases_monty,
2024
- "test_cases",
2025
- r"^.*\.py$",
2026
- run_test_cases_cpython,
2027
- "test_cases",
2028
- r"^.*\.py$",
2029
- );