Created
August 14, 2023 18:54
-
-
Save hamelin/8248912c06493f668a281dc76cdea782 to your computer and use it in GitHub Desktop.
Pytest emulation directly in a notebook
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"id": "740bd213-ff52-4e48-8ffb-cdbc0df1d43d", | |
"metadata": { | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"import ast\n", | |
"import inspect as ins\n", | |
"import itertools as it\n", | |
"from pathlib import Path" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"id": "3bc959de-88da-48ae-8d40-ede18ad7e09d", | |
"metadata": { | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"TESTS = {}" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"id": "7840b922-1189-4b8d-a578-b874c54eae2b", | |
"metadata": { | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"class pytest:\n", | |
" @staticmethod\n", | |
" def fixture(fn):\n", | |
" return fn(**{p: globals()[p] for p in ins.signature(fn).parameters.keys()})\n", | |
"\n", | |
" class mark:\n", | |
" @staticmethod\n", | |
" def parametrize(names_, seqs_params):\n", | |
" def parametrize_test(fn_test):\n", | |
" assert fn_test.__name__ in TESTS\n", | |
" names = names_.split(\",\")\n", | |
" paramz_new = []\n", | |
" for paramz in TESTS[fn_test.__name__]:\n", | |
" for i, values in enumerate(seqs_params):\n", | |
" if len(values) != len(names):\n", | |
" raise ValueError(\n", | |
" f\"Parameter sequence {i} carries {len(values)} \"\n", | |
" f\"values, but {len(values)} names must be bound.\"\n", | |
" )\n", | |
" paramz_new.append({**paramz, **dict(zip(names, values))})\n", | |
" TESTS[fn_test.__name__] = paramz_new\n", | |
" return fn_test\n", | |
" return parametrize_test" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"id": "abde42d5-9c30-408b-bd5a-cc1bde3b985a", | |
"metadata": { | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"class request:\n", | |
" @staticmethod\n", | |
" def getfixturevalue(name):\n", | |
" G = globals()\n", | |
" if name not in G:\n", | |
" raise NameError(f\"Fixture {name} is not defined.\")\n", | |
" return G[name]" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"id": "84eb5f19-faa3-40f7-99f7-902f8b264018", | |
"metadata": { | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"class TestDiscoverer(ast.NodeTransformer):\n", | |
"\n", | |
" def visit_FunctionDef(self, node):\n", | |
" if node.name.startswith(\"test_\"):\n", | |
" TESTS[node.name] = [{}]\n", | |
" return node\n", | |
"\n", | |
"\n", | |
"ipy = get_ipython()\n", | |
"ipy.ast_transformers.clear()\n", | |
"ipy.ast_transformers.append(TestDiscoverer())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"id": "ada94aeb-6818-4433-bfb0-a581fae4fec2", | |
"metadata": { | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"def printer_result(name, seq_paramz, width):\n", | |
" if len(seq_paramz) < 2:\n", | |
" return lambda _, label: print(f\"{name:{width}} ... {label}\")\n", | |
"\n", | |
" print(f\"{name}:\")\n", | |
" return lambda i, label: print(f\"{f' {i:4d}':{width}} ... {label}\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"id": "9f92ea5e-8c0d-4633-94af-4a5151c5094c", | |
"metadata": { | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"if \"run_tests\" in globals():\n", | |
" try:\n", | |
" get_ipython().events.unregister(\"post_run_cell\", run_tests)\n", | |
" except ValueError as err:\n", | |
" if \"is not registered\" not in str(err):\n", | |
" raise\n", | |
"\n", | |
"\n", | |
"def run_tests(_):\n", | |
" G = globals()\n", | |
" if not TESTS:\n", | |
" return\n", | |
" width = max(len(n) for n in it.chain(TESTS.keys(), [f\" xxxx\"]))\n", | |
" print(\"=\" * 88)\n", | |
" for name, seq_paramz in TESTS.items():\n", | |
" if name not in G:\n", | |
" print(f\"{name:{width}}: missing\")\n", | |
" continue\n", | |
" assert seq_paramz\n", | |
" print_result = printer_result(name, seq_paramz, width)\n", | |
" fn_test = G[name]\n", | |
" sig = ins.signature(fn_test)\n", | |
" for i, paramz in enumerate(seq_paramz):\n", | |
" params = {**paramz}\n", | |
" for param in sig.parameters.keys():\n", | |
" if param not in params:\n", | |
" if param in G:\n", | |
" params[param] = G[param]\n", | |
" else:\n", | |
" raise ValueError(\n", | |
" f\"Missing fixture {param} necessary for test {name}\"\n", | |
" )\n", | |
"\n", | |
" try:\n", | |
" fn_test(**params)\n", | |
" print_result(i, \"OK\")\n", | |
" except AssertionError as err:\n", | |
" print_result(i, f\"fail at line {err.__traceback__.tb_next.tb_lineno}\")\n", | |
" except Exception as err:\n", | |
" print_result(i, f\"{str(err)} [{type(err).__name__}] at line {err.__traceback__.tb_next.tb_lineno}\")\n", | |
"\n", | |
"\n", | |
"get_ipython().events.register(\"post_run_cell\", run_tests)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"id": "c46bd043-be7d-4d2f-8204-b46727a1204e", | |
"metadata": { | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"test/conftest.py\n" | |
] | |
} | |
], | |
"source": [ | |
"for path_conftest in Path(\".\").glob(\"**/conftest.py\"):\n", | |
" if not str(path_conftest).startswith(\".\"):\n", | |
" print(path_conftest)\n", | |
" with open(\"test/conftest.py\", \"r\", encoding=\"utf-8\") as file:\n", | |
" code = \"\\n\".join([line.rstrip() for line in file if \"import pytest\" not in line])\n", | |
" exec(code)" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3 (ipykernel)", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.11.4" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 5 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment