From 6698792c4625e820b76c0f31eec00e9c1cfc6135 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Fri, 25 Mar 2022 10:38:11 -0400 Subject: [PATCH 1/4] add static analysis of hw0 --- hw_0.ipynb | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/hw_0.ipynb b/hw_0.ipynb index 88ca68f2..17c98ac6 100644 --- a/hw_0.ipynb +++ b/hw_0.ipynb @@ -184,6 +184,88 @@ "# your code goes here" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Autograding\n", + "\n", + "Using [this approach](https://stackoverflow.com/a/70920396/358804)." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "IPython.notebook.save_checkpoint();\n", + "IPython.notebook.kernel.execute('nb_name = \"' + IPython.notebook.notebook_name + '\"')\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%javascript\n", + "IPython.notebook.save_checkpoint();\n", + "IPython.notebook.kernel.execute('nb_name = \"' + IPython.notebook.notebook_name + '\"')" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "you don't have enough calls to input()", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", + "Input \u001b[0;32mIn [21]\u001b[0m, in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 22\u001b[0m tree \u001b[38;5;241m=\u001b[39m ast\u001b[38;5;241m.\u001b[39mparse(script)\n\u001b[1;32m 23\u001b[0m checker\u001b[38;5;241m.\u001b[39mvisit(tree)\n\u001b[0;32m---> 25\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m checker\u001b[38;5;241m.\u001b[39mnum_inputs \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m8\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124myou don\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mt have enough calls to input()\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 26\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtests passed!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mAssertionError\u001b[0m: you don't have enough calls to input()" + ] + } + ], + "source": [ + "import ast\n", + "import nbconvert\n", + "import nbformat\n", + "import os\n", + "\n", + "\n", + "class InputChecker(ast.NodeVisitor):\n", + " def __init__(self):\n", + " self.num_inputs = 0\n", + "\n", + " def visit_Call(self, node):\n", + " if isinstance(node.func, ast.Name) and node.func.id == \"input\":\n", + " self.num_inputs += 1\n", + "\n", + "\n", + "nb_full_path = os.path.join(os.getcwd(), nb_name)\n", + "notebook = nbformat.read(nb_full_path, as_version=4)\n", + "exporter = nbconvert.exporters.PythonExporter\n", + "script, _ = nbconvert.exporters.export(exporter, notebook)\n", + "\n", + "checker = InputChecker()\n", + "tree = ast.parse(script)\n", + "checker.visit(tree)\n", + "\n", + "assert checker.num_inputs >= 8, \"you don't have enough calls to input()\"\n", + "print(\"tests passed!\")" + ] + }, { "cell_type": "markdown", "metadata": {}, From fbc7e492d814349f3f7594d28d650513ba7064b5 Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Fri, 25 Mar 2022 11:06:45 -0400 Subject: [PATCH 2/4] HW0: run autograding via pytest --- hw_0.ipynb | 91 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/hw_0.ipynb b/hw_0.ipynb index 17c98ac6..1d5a08fa 100644 --- a/hw_0.ipynb +++ b/hw_0.ipynb @@ -195,7 +195,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 1, "metadata": {}, "outputs": [ { @@ -220,50 +220,91 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 2, "metadata": { "scrolled": true }, - "outputs": [ - { - "ename": "AssertionError", - "evalue": "you don't have enough calls to input()", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", - "Input \u001b[0;32mIn [21]\u001b[0m, in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 22\u001b[0m tree \u001b[38;5;241m=\u001b[39m ast\u001b[38;5;241m.\u001b[39mparse(script)\n\u001b[1;32m 23\u001b[0m checker\u001b[38;5;241m.\u001b[39mvisit(tree)\n\u001b[0;32m---> 25\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m checker\u001b[38;5;241m.\u001b[39mnum_inputs \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m8\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124myou don\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mt have enough calls to input()\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 26\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtests passed!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "\u001b[0;31mAssertionError\u001b[0m: you don't have enough calls to input()" - ] - } - ], + "outputs": [], "source": [ "import ast\n", + "import ipytest\n", "import nbconvert\n", "import nbformat\n", "import os\n", "\n", "\n", + "ipytest.autoconfig()\n", + "\n", + "\n", "class InputChecker(ast.NodeVisitor):\n", " def __init__(self):\n", " self.num_inputs = 0\n", "\n", " def visit_Call(self, node):\n", " if isinstance(node.func, ast.Name) and node.func.id == \"input\":\n", - " self.num_inputs += 1\n", + " self.num_inputs += 1" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31mF\u001b[0m\u001b[31m [100%]\u001b[0m\n", + "============================================= FAILURES =============================================\n", + "\u001b[31m\u001b[1m_________________________________________ test_num_inputs __________________________________________\u001b[0m\n", + "\n", + "tree = \n", + "\n", + " \u001b[94mdef\u001b[39;49;00m \u001b[92mtest_num_inputs\u001b[39;49;00m(tree):\n", + " checker = InputChecker()\n", + " checker.visit(tree)\n", + " \n", + "> \u001b[94massert\u001b[39;49;00m checker.num_inputs >= \u001b[94m8\u001b[39;49;00m, \u001b[33m\"\u001b[39;49;00m\u001b[33myou don\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33mt have enough calls to input()\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\n", + "\u001b[1m\u001b[31mE AssertionError: you don't have enough calls to input()\u001b[0m\n", + "\u001b[1m\u001b[31mE assert 2 >= 8\u001b[0m\n", + "\u001b[1m\u001b[31mE + where 2 = <__main__.InputChecker object at 0x10d084fa0>.num_inputs\u001b[0m\n", + "\n", + "\u001b[1m\u001b[31m/var/folders/kg/1ys0dccx4237f5wsd_w10dt80000gn/T/ipykernel_65587/682324548.py\u001b[0m:27: AssertionError\n", + "===================================== short test summary info ======================================\n", + "FAILED tmpciht1t3f.py::test_num_inputs - AssertionError: you don't have enough calls to input()\n" + ] + } + ], + "source": [ + "%%ipytest -qq\n", + "\n", + "import pytest\n", + "\n", + "\n", + "\n", + "@pytest.fixture()\n", + "def notebook():\n", + " nb_full_path = os.path.join(os.getcwd(), nb_name)\n", + " return nbformat.read(nb_full_path, as_version=4)\n", + "\n", + "\n", + "@pytest.fixture()\n", + "def script(notebook):\n", + " exporter = nbconvert.exporters.PythonExporter\n", + " script, _ = nbconvert.exporters.export(exporter, notebook)\n", + " return script\n", "\n", "\n", - "nb_full_path = os.path.join(os.getcwd(), nb_name)\n", - "notebook = nbformat.read(nb_full_path, as_version=4)\n", - "exporter = nbconvert.exporters.PythonExporter\n", - "script, _ = nbconvert.exporters.export(exporter, notebook)\n", + "@pytest.fixture()\n", + "def tree(script):\n", + " return ast.parse(script)\n", "\n", - "checker = InputChecker()\n", - "tree = ast.parse(script)\n", - "checker.visit(tree)\n", "\n", - "assert checker.num_inputs >= 8, \"you don't have enough calls to input()\"\n", - "print(\"tests passed!\")" + "def test_num_inputs(tree):\n", + " checker = InputChecker()\n", + " checker.visit(tree)\n", + " \n", + " assert checker.num_inputs >= 8, \"you don't have enough calls to input()\"" ] }, { From bcf252c3ea171011c4018568d19ff6ff3311f41c Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Fri, 25 Mar 2022 11:21:36 -0400 Subject: [PATCH 3/4] move autograder helper code into its own file --- extras/__init__.py | 0 extras/scripts/hw_0_helper.py | 29 ++++++++++++++ hw_0.ipynb | 75 ++++------------------------------- 3 files changed, 37 insertions(+), 67 deletions(-) create mode 100644 extras/__init__.py create mode 100644 extras/scripts/hw_0_helper.py diff --git a/extras/__init__.py b/extras/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/extras/scripts/hw_0_helper.py b/extras/scripts/hw_0_helper.py new file mode 100644 index 00000000..fc194263 --- /dev/null +++ b/extras/scripts/hw_0_helper.py @@ -0,0 +1,29 @@ +import ast +import os +import pytest +from .nb_helper import * + + +class InputChecker(ast.NodeVisitor): + def __init__(self): + self.num_inputs = 0 + + def visit_Call(self, node): + if isinstance(node.func, ast.Name) and node.func.id == "input": + self.num_inputs += 1 + + +@pytest.fixture() +def notebook(): + nb_full_path = os.path.join(os.getcwd(), "hw_0.ipynb") + return read_notebook(nb_full_path) + + +@pytest.fixture() +def script(notebook): + return notebook_to_script(notebook) + + +@pytest.fixture() +def tree(script): + return ast.parse(script) diff --git a/hw_0.ipynb b/hw_0.ipynb index 1d5a08fa..afad0b1e 100644 --- a/hw_0.ipynb +++ b/hw_0.ipynb @@ -196,58 +196,18 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "IPython.notebook.save_checkpoint();\n", - "IPython.notebook.kernel.execute('nb_name = \"' + IPython.notebook.notebook_name + '\"')\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%javascript\n", - "IPython.notebook.save_checkpoint();\n", - "IPython.notebook.kernel.execute('nb_name = \"' + IPython.notebook.notebook_name + '\"')" - ] - }, - { - "cell_type": "code", - "execution_count": 2, "metadata": { "scrolled": true }, "outputs": [], "source": [ - "import ast\n", "import ipytest\n", - "import nbconvert\n", - "import nbformat\n", - "import os\n", - "\n", - "\n", - "ipytest.autoconfig()\n", - "\n", - "\n", - "class InputChecker(ast.NodeVisitor):\n", - " def __init__(self):\n", - " self.num_inputs = 0\n", - "\n", - " def visit_Call(self, node):\n", - " if isinstance(node.func, ast.Name) and node.func.id == \"input\":\n", - " self.num_inputs += 1" + "ipytest.autoconfig()" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -258,7 +218,7 @@ "============================================= FAILURES =============================================\n", "\u001b[31m\u001b[1m_________________________________________ test_num_inputs __________________________________________\u001b[0m\n", "\n", - "tree = \n", + "tree = \n", "\n", " \u001b[94mdef\u001b[39;49;00m \u001b[92mtest_num_inputs\u001b[39;49;00m(tree):\n", " checker = InputChecker()\n", @@ -267,43 +227,24 @@ "> \u001b[94massert\u001b[39;49;00m checker.num_inputs >= \u001b[94m8\u001b[39;49;00m, \u001b[33m\"\u001b[39;49;00m\u001b[33myou don\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33mt have enough calls to input()\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\n", "\u001b[1m\u001b[31mE AssertionError: you don't have enough calls to input()\u001b[0m\n", "\u001b[1m\u001b[31mE assert 2 >= 8\u001b[0m\n", - "\u001b[1m\u001b[31mE + where 2 = <__main__.InputChecker object at 0x10d084fa0>.num_inputs\u001b[0m\n", + "\u001b[1m\u001b[31mE + where 2 = .num_inputs\u001b[0m\n", "\n", - "\u001b[1m\u001b[31m/var/folders/kg/1ys0dccx4237f5wsd_w10dt80000gn/T/ipykernel_65587/682324548.py\u001b[0m:27: AssertionError\n", + "\u001b[1m\u001b[31m/var/folders/kg/1ys0dccx4237f5wsd_w10dt80000gn/T/ipykernel_67029/2901258849.py\u001b[0m:8: AssertionError\n", "===================================== short test summary info ======================================\n", - "FAILED tmpciht1t3f.py::test_num_inputs - AssertionError: you don't have enough calls to input()\n" + "FAILED tmpyoyk9z3m.py::test_num_inputs - AssertionError: you don't have enough calls to input()\n" ] } ], "source": [ "%%ipytest -qq\n", "\n", - "import pytest\n", - "\n", - "\n", - "\n", - "@pytest.fixture()\n", - "def notebook():\n", - " nb_full_path = os.path.join(os.getcwd(), nb_name)\n", - " return nbformat.read(nb_full_path, as_version=4)\n", - "\n", - "\n", - "@pytest.fixture()\n", - "def script(notebook):\n", - " exporter = nbconvert.exporters.PythonExporter\n", - " script, _ = nbconvert.exporters.export(exporter, notebook)\n", - " return script\n", - "\n", - "\n", - "@pytest.fixture()\n", - "def tree(script):\n", - " return ast.parse(script)\n", + "from extras.scripts.hw_0_helper import *\n", "\n", "\n", "def test_num_inputs(tree):\n", " checker = InputChecker()\n", " checker.visit(tree)\n", - " \n", + "\n", " assert checker.num_inputs >= 8, \"you don't have enough calls to input()\"" ] }, From 4351875ec5d587016136ae24a4d397d0ef9756ba Mon Sep 17 00:00:00 2001 From: Aidan Feldman Date: Fri, 25 Mar 2022 11:39:16 -0400 Subject: [PATCH 4/4] make the functional call checker support any function name --- extras/scripts/hw_0_helper.py | 12 +++++++----- hw_0.ipynb | 24 +++++++++++++++--------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/extras/scripts/hw_0_helper.py b/extras/scripts/hw_0_helper.py index fc194263..390092d8 100644 --- a/extras/scripts/hw_0_helper.py +++ b/extras/scripts/hw_0_helper.py @@ -4,13 +4,15 @@ from .nb_helper import * -class InputChecker(ast.NodeVisitor): - def __init__(self): - self.num_inputs = 0 +# https://sadh.life/post/ast/ +class FuncCallChecker(ast.NodeVisitor): + def __init__(self, func: str): + self.func = func + self.num_calls = 0 def visit_Call(self, node): - if isinstance(node.func, ast.Name) and node.func.id == "input": - self.num_inputs += 1 + if isinstance(node.func, ast.Name) and node.func.id == self.func: + self.num_calls += 1 @pytest.fixture() diff --git a/hw_0.ipynb b/hw_0.ipynb index afad0b1e..8c43739a 100644 --- a/hw_0.ipynb +++ b/hw_0.ipynb @@ -207,7 +207,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -218,20 +218,26 @@ "============================================= FAILURES =============================================\n", "\u001b[31m\u001b[1m_________________________________________ test_num_inputs __________________________________________\u001b[0m\n", "\n", - "tree = \n", + "tree = \n", "\n", " \u001b[94mdef\u001b[39;49;00m \u001b[92mtest_num_inputs\u001b[39;49;00m(tree):\n", - " checker = InputChecker()\n", + " checker = FuncCallChecker(\u001b[33m\"\u001b[39;49;00m\u001b[33minput\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\n", " checker.visit(tree)\n", " \n", - "> \u001b[94massert\u001b[39;49;00m checker.num_inputs >= \u001b[94m8\u001b[39;49;00m, \u001b[33m\"\u001b[39;49;00m\u001b[33myou don\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33mt have enough calls to input()\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\n", + "> \u001b[94massert\u001b[39;49;00m checker.num_calls >= \u001b[94m8\u001b[39;49;00m, \u001b[33m\"\u001b[39;49;00m\u001b[33myou don\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33mt have enough calls to input()\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\n", "\u001b[1m\u001b[31mE AssertionError: you don't have enough calls to input()\u001b[0m\n", "\u001b[1m\u001b[31mE assert 2 >= 8\u001b[0m\n", - "\u001b[1m\u001b[31mE + where 2 = .num_inputs\u001b[0m\n", + "\u001b[1m\u001b[31mE + where 2 = .num_calls\u001b[0m\n", + "\n", + "\u001b[1m\u001b[31m/var/folders/kg/1ys0dccx4237f5wsd_w10dt80000gn/T/ipykernel_68325/4145485001.py\u001b[0m:8: AssertionError\n", + "\u001b[33m========================================= warnings summary =========================================\u001b[0m\n", + "tmpc439hpt9.py::test_num_inputs\n", + " /usr/local/Caskroom/miniconda/base/envs/python-public-policy/lib/python3.10/site-packages/IPython/core/inputsplitter.py:21: DeprecationWarning: IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`\n", + " warn('IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`',\n", "\n", - "\u001b[1m\u001b[31m/var/folders/kg/1ys0dccx4237f5wsd_w10dt80000gn/T/ipykernel_67029/2901258849.py\u001b[0m:8: AssertionError\n", + "-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n", "===================================== short test summary info ======================================\n", - "FAILED tmpyoyk9z3m.py::test_num_inputs - AssertionError: you don't have enough calls to input()\n" + "FAILED tmpc439hpt9.py::test_num_inputs - AssertionError: you don't have enough calls to input()\n" ] } ], @@ -242,10 +248,10 @@ "\n", "\n", "def test_num_inputs(tree):\n", - " checker = InputChecker()\n", + " checker = FuncCallChecker(\"input\")\n", " checker.visit(tree)\n", "\n", - " assert checker.num_inputs >= 8, \"you don't have enough calls to input()\"" + " assert checker.num_calls >= 8, \"you don't have enough calls to input()\"" ] }, {