From dca104248d9113d34db16e72a4ba12aa833842cb Mon Sep 17 00:00:00 2001 From: Daniel Mouris Date: Mon, 7 Oct 2019 13:18:37 -0600 Subject: [PATCH 1/6] Added pytest for test runner, changed documentation --- README.md | 2 +- requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb492aac..6d7bbcfa 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ This is still in alpha stage so there might be some bugs. If you want to impleme ### Unit Tests To run tests, run this at the root directory: ``` -python -m tests.test_linter +pytest ``` ### Future Goals: diff --git a/requirements.txt b/requirements.txt index 7e03c211..37aec61f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ six==1.11.0 typed-ast==1.1.0 Werkzeug==0.15.3 wrapt==1.10.11 +pytest==5.2.1 From cc2cbb8c4cab06e0c253d28bd2c9f0527117eb61 Mon Sep 17 00:00:00 2001 From: Daniel Mouris Date: Mon, 7 Oct 2019 14:07:37 -0600 Subject: [PATCH 2/6] Added index template test, is_os_linux test --- PythonBuddy/app.py | 10 +++++----- tests/test_app.py | 36 ++++++++++++++++++++++++++++++++++++ tests/test_linter.py | 4 ---- 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 tests/test_app.py diff --git a/PythonBuddy/app.py b/PythonBuddy/app.py index 8cee129c..a6a24ee0 100644 --- a/PythonBuddy/app.py +++ b/PythonBuddy/app.py @@ -17,10 +17,10 @@ from multiprocessing import Pool, cpu_count -is_linux = True - -if os.name == "nt": - is_linux = False +def is_os_linux(): + if os.name == "nt": + return False + return True # Configure Flask App # Remember to change the SECRET_KEY! @@ -180,7 +180,7 @@ def process_error(error): # Detect OS line_num = None - if is_linux: + if is_os_linux(): try: line_num = error.split(":")[1] except Exception as e: diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 00000000..fcaa7671 --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,36 @@ +from datetime import datetime +import flask +import unittest +from unittest.mock import patch +from PythonBuddy.app import * + + +class TestIndexPage(unittest.TestCase): + def setUp(self): + self.app = app.test_client() + + def test_index(self): + index_page = self.app.get('/') + self.assertEqual(index_page.status, '200 OK') + self.assertTrue(b'Python Linter Online' in index_page.data) + + @patch('datetime.datetime') + def test_index_session(self, datetime_mock): + current_time = datetime(2019, 10, 7, 13, 46, 33, 824712) + datetime_mock.now.return_value = current_time + with app.test_client() as test_client: + index_page = test_client.get('/') + + self.assertEqual(flask.session['count'], 0) + self.assertEqual(flask.session['time_now'].date(), + current_time.date()) + + +class TestUtilities(unittest.TestCase): + @patch('PythonBuddy.app.os') + def test_is_os_linux(self, os_name_patch): + os_name_patch.name = "nt" + self.assertFalse(is_os_linux()) + + os_name_patch.name = "ubuntu :)" + self.assertTrue(is_os_linux()) diff --git a/tests/test_linter.py b/tests/test_linter.py index 93ea3c2f..d5609c31 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -21,7 +21,3 @@ def test_process_error(self): self.assertEqual(process_error(test_error), {'code': 'E1120', 'error': 'no-value-for-parameter', 'message': "No value for argument 'baz' in function", 'line': '3', 'error_info': ' \r Occurs when a function call passes too few arguments.\r'}) self.assertEqual(process_error(None), None) self.assertEqual(process_error(""), None) - -if __name__ == '__main__': - # print(process_error("s")) - unittest.main() From 2772056049db16c50cbfc4296b550a2534e38032 Mon Sep 17 00:00:00 2001 From: Daniel Mouris Date: Tue, 8 Oct 2019 08:25:47 -0600 Subject: [PATCH 3/6] Simplified session test to see if it has keys --- tests/test_app.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/test_app.py b/tests/test_app.py index fcaa7671..71000604 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -14,16 +14,12 @@ def test_index(self): self.assertEqual(index_page.status, '200 OK') self.assertTrue(b'Python Linter Online' in index_page.data) - @patch('datetime.datetime') - def test_index_session(self, datetime_mock): - current_time = datetime(2019, 10, 7, 13, 46, 33, 824712) - datetime_mock.now.return_value = current_time + def test_index_session(self): with app.test_client() as test_client: index_page = test_client.get('/') - self.assertEqual(flask.session['count'], 0) - self.assertEqual(flask.session['time_now'].date(), - current_time.date()) + self.assertTrue('count' in flask.session) + self.assertTrue('time_now' in flask.session) class TestUtilities(unittest.TestCase): From 1d942d9f268e82aacf9e6ca1c4a0a22451d067f1 Mon Sep 17 00:00:00 2001 From: Daniel Mouris Date: Tue, 8 Oct 2019 19:16:29 -0600 Subject: [PATCH 4/6] Add reStructuredText to existing tests --- tests/test_app.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/test_app.py b/tests/test_app.py index 71000604..0122fdb9 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -6,15 +6,19 @@ class TestIndexPage(unittest.TestCase): - def setUp(self): - self.app = app.test_client() - def test_index(self): - index_page = self.app.get('/') + """Test the index page that it is displayed + :param self: instance of the current test. + """ + test_client = app.test_client() + index_page = test_client.get('/') self.assertEqual(index_page.status, '200 OK') self.assertTrue(b'Python Linter Online' in index_page.data) def test_index_session(self): + """Test the flask session on the index page. + :param self: instance of the current test. + """ with app.test_client() as test_client: index_page = test_client.get('/') @@ -25,6 +29,10 @@ def test_index_session(self): class TestUtilities(unittest.TestCase): @patch('PythonBuddy.app.os') def test_is_os_linux(self, os_name_patch): + """Test the linux check in the PythonBuddy app + :param self: instance of the current test. + :param os_name_patch: patch of the os in the pybuddy app + """ os_name_patch.name = "nt" self.assertFalse(is_os_linux()) From 015a7f4533d151ac9d459131be0165742b6ec470 Mon Sep 17 00:00:00 2001 From: Daniel Mouris Date: Thu, 10 Oct 2019 22:43:32 -0600 Subject: [PATCH 5/6] Add coverage to the project, some gitignore as well --- .gitignore | 3 +++ .travis.yml | 2 +- README.md | 5 +++++ requirements.txt | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fbac88f4..cfbc8900 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ venv .DS_STORE *.pyc .vscode +assets/ +htmlcov/ +.coverage \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index e4c4534d..67b16fba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ python: install: "pip install -r requirements.txt" # command to run tests script: - - python -m tests.test_linter + - pytest diff --git a/README.md b/README.md index 6d7bbcfa..63d5e4bf 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,11 @@ To run tests, run this at the root directory: ``` pytest ``` +For coverage run the following command in the root directory: +``` +pytest --cov-report html --cov=PythonBuddy tests/ +``` + ### Future Goals: - Make easily embeddable for MOOCs like edX and other education platform diff --git a/requirements.txt b/requirements.txt index 37aec61f..4e5ed667 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,5 @@ typed-ast==1.1.0 Werkzeug==0.15.3 wrapt==1.10.11 pytest==5.2.1 +pytest-cov==2.8.1 +pytest-html==2.0.0 From 1aed201336bd410cba46fd75a2815c7e3fcf8646 Mon Sep 17 00:00:00 2001 From: Daniel Mouris Date: Thu, 10 Oct 2019 22:47:56 -0600 Subject: [PATCH 6/6] Add Tests for file checking, evaluate_pylint, and slow down, minor changes to app --- PythonBuddy/app.py | 6 ++-- tests/test_app.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/PythonBuddy/app.py b/PythonBuddy/app.py index a6a24ee0..5ab0f7c2 100644 --- a/PythonBuddy/app.py +++ b/PythonBuddy/app.py @@ -126,7 +126,7 @@ def evaluate_pylint(text): for t in text: f.write(t) f.flush() - except Exception as e: + except KeyError as e: with tempfile.NamedTemporaryFile(delete=False) as temp: session["file_name"] = temp.name for t in text: @@ -288,11 +288,13 @@ def format_errors(pylint_text): # # return "No information at the moment" +def remove_temp_code_file(): + os.remove(session["file_name"]) @socketio.on('disconnect', namespace='/check_disconnect') def disconnect(): """Remove temp file associated with current session""" - os.remove(session["file_name"]) + remove_temp_code_file() if __name__ == "__main__": diff --git a/tests/test_app.py b/tests/test_app.py index 0122fdb9..2b459986 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -3,6 +3,7 @@ import unittest from unittest.mock import patch from PythonBuddy.app import * +from os import path class TestIndexPage(unittest.TestCase): @@ -38,3 +39,90 @@ def test_is_os_linux(self, os_name_patch): os_name_patch.name = "ubuntu :)" self.assertTrue(is_os_linux()) + + +class TestCodeRunning(unittest.TestCase): + def setUp(self): + self.code_example = "for i in range(5):\n print(i)\n" + self.code_example_modified = "print(\"Oh hai Mark\")" + self.error_message = [ + { + "code": self.code_example, + "error": "", + "message": "", + "line": "", + "error_info": "", + } + ] + + @patch('PythonBuddy.app.evaluate_pylint') + def test_check_code_endpoint_no_evaluate_pylint(self, evaluate_pytlint_patch): + """Test the check code endpoint + :param self: instance of the current test. + :param evaluate_pytlint_patch: patch of the os in the evaluate_pytlint + testing this separately. + """ + evaluate_pytlint_patch.return_value = self.error_message + with app.test_client() as test_client: + check_code_page = test_client.post('/check_code', data={ + "text": self.code_example + }) + self.assertEqual(check_code_page.status, '200 OK') + self.assertTrue('code' in flask.session) + self.assertEqual(flask.session['code'], self.code_example) + + @patch('PythonBuddy.app.slow') + def test_run_code_slow(self, slow_patch): + """Test that when code is running too much + :param self: instance of current test + :param slow_patch: path the slow method, test separately + """ + slow_patch.return_value = True + test_client = app.test_client() + check_run_code = test_client.post('/run_code', data={}) + self.assertEqual(check_run_code.status, '200 OK') + self.assertTrue(b"Running code too much" in check_run_code.data) + + def test_evaluate_pylint_test_file_creation_deletion_and_contents(self): + """Test the evaluate pylint method in depth. + :param self: instance of the current test. + + first check if the file will get created (keyerror thrown) + second check if code in file will get modified + """ + with app.test_client() as test_client: + test_client.get('/') # this will set the count + test_client.post('/check_code', data={ + "text": self.code_example + }) + + # first test + # this will check that the exception is raised and + # a new file is created + test_client.post('/run_code') + self.assertTrue('file_name' in flask.session) + + # this will check that the exception is raised and + # a new file is created + test_client.post('/check_code', data={ + "text": self.code_example_modified + }) + # check the code is modified + temp_code_file = open(flask.session['file_name'], "r") + self.assertEqual(temp_code_file.read(), self.code_example_modified) + + def test_if_file_created_and_deleted(self): + """Test if the file will get created and deleted. + :param self: instance of the current test. + """ + with app.test_client() as test_client: + + test_client.post('/check_code', data={ + "text": self.code_example + }) + # check if the file still exists + self.assertTrue(path.exists(flask.session['file_name'])) + remove_temp_code_file() + + # ensure file is deleted + self.assertFalse(path.exists(flask.session['file_name']))