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 4acbffc0..fa4d8cb0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ install: - pip install coveralls # command to run tests script: + - pytest - coverage run --source=PythonBuddy -m unittest tests/test_linter.py - coverage report -m - coveralls diff --git a/PythonBuddy/app.py b/PythonBuddy/app.py index 8cee129c..5ab0f7c2 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! @@ -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: @@ -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: @@ -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/README.md b/README.md index 1c7dde1f..dba35934 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,13 @@ 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 ``` +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 7e03c211..4e5ed667 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,6 @@ six==1.11.0 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 diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 00000000..2b459986 --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,128 @@ +from datetime import datetime +import flask +import unittest +from unittest.mock import patch +from PythonBuddy.app import * +from os import path + + +class TestIndexPage(unittest.TestCase): + def test_index(self): + """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('/') + + self.assertTrue('count' in flask.session) + self.assertTrue('time_now' in flask.session) + + +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()) + + 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'])) diff --git a/tests/test_linter.py b/tests/test_linter.py index 72d53d35..44e046b7 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -22,7 +22,6 @@ def test_process_error(self): self.assertEqual(process_error(None), None) self.assertEqual(process_error(""), None) - def test_no_errors(self): """ Asserts format_errors function returns None when @@ -36,8 +35,5 @@ def test_no_errors(self): None ) - - if __name__ == '__main__': - # print(process_error("s")) unittest.main()