From 755e402c8204f1d79f14df23b2fb53404169f98d Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 31 Dec 2017 11:01:08 -0200 Subject: [PATCH 01/93] Fix default value syntax for docopt entry for 'spool' parameter. Now, default value is set as 200. --- pytest_watch/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_watch/command.py b/pytest_watch/command.py index f87a248..2a72caf 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -29,7 +29,7 @@ --pdb Start the interactive Python debugger on errors. This also enables --wait to prevent pdb interruption. --spool Re-run after a delay (in milliseconds), allowing for - more file system events to queue up (default: 200 ms). + more file system events to queue up [default: 200]. -p --poll Use polling instead of OS events (useful in VMs). -v --verbose Increase verbosity of the output. -q --quiet Decrease verbosity of the output (precedence over -v). From dc82b400966ab335a332dd99c053dfe375b998d3 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 31 Dec 2017 11:03:57 -0200 Subject: [PATCH 02/93] Add initial tests for command line arguments. --- pytest_watch/tests/__init__.py | 0 pytest_watch/tests/test_main.py | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 pytest_watch/tests/__init__.py create mode 100644 pytest_watch/tests/test_main.py diff --git a/pytest_watch/tests/__init__.py b/pytest_watch/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py new file mode 100644 index 0000000..177d9dd --- /dev/null +++ b/pytest_watch/tests/test_main.py @@ -0,0 +1,38 @@ +import unittest + +from pytest_watch.command import main + +from unittest.mock import patch + + +class TestCLIArguments(unittest.TestCase): + + @patch("pytest_watch.command.watch") + def test_default_parameters(self, watch_callee): + watch_callee.side_effect = lambda *args, **kwargs: 0 + + default_args = dict( + directories=[], + ignore=[], + extensions=None, + beep_on_failure=True, + auto_clear=False, + wait=False, + beforerun=None, + afterrun=None, + onpass=None, + onfail=None, + onexit=None, + runner=None, + spool=200, + poll=False, + verbose=False, + quiet=False, + pytest_args=[] + ) + + main([]) + + watch_callee.assert_called_once() + watch_callee.assert_called_once_with(**default_args) + From 8e63df5ca312f7de0e090f821ff50df57d8d0afe Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 31 Dec 2017 13:50:35 -0200 Subject: [PATCH 03/93] git ignore coverage directory --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 28582d3..f5ffc20 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ env .DS_Store Desktop.ini Thumbs.db +.coverage +.coverage.* +venv From 314dc78ea62751a2914d2d2780a437f6a71f2928 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 31 Dec 2017 13:51:36 -0200 Subject: [PATCH 04/93] simple comment on pytest config argument --- pytest_watch/command.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest_watch/command.py b/pytest_watch/command.py index 2a72caf..4f1ea6d 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -76,6 +76,8 @@ def main(argv=None): # Adjust pytest and --collect-only args for ignore in args['--ignore']: pytest_args.extend(['--ignore', ignore]) + + # Set pytest config file if args['--config']: pytest_args.extend(['-c', args['--config']]) From a3286f93bd2e7190c720142ea2888ad93cd48fd2 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 31 Dec 2017 13:52:47 -0200 Subject: [PATCH 05/93] Added 'spool' parameter validation and coverage tests, considering default value as 200 --- pytest_watch/command.py | 15 +++++++----- pytest_watch/tests/test_main.py | 41 +++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/pytest_watch/command.py b/pytest_watch/command.py index 4f1ea6d..89c3c12 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -100,12 +100,15 @@ def main(argv=None): # Parse numeric arguments spool = args['--spool'] - if spool is not None: - try: - spool = int(spool) - except ValueError: - sys.stderr.write('Error: Spool must be an integer.\n') - return 2 + try: + spool = int(spool) + except ValueError: + sys.stderr.write('Error: Spool must be an integer.\n') + return 2 + + if spool < 0: + sys.stderr.write("Error: Spool value must be positive integer") + return 2 # Run pytest and watch for changes return watch(directories=directories, diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index 177d9dd..9c04b9e 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -2,15 +2,16 @@ from pytest_watch.command import main -from unittest.mock import patch +try: + from unittest.mock import patch +except ImportError: + from mock import patch class TestCLIArguments(unittest.TestCase): - @patch("pytest_watch.command.watch") + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_default_parameters(self, watch_callee): - watch_callee.side_effect = lambda *args, **kwargs: 0 - default_args = dict( directories=[], ignore=[], @@ -36,3 +37,35 @@ def test_default_parameters(self, watch_callee): watch_callee.assert_called_once() watch_callee.assert_called_once_with(**default_args) + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_zero_spool_value(self, watch_callee): + main("--spool 0".split()) + self.assertIn("spool", watch_callee.call_args[1]) + self.assertEqual(0, watch_callee.call_args[1]["spool"]) + watch_callee.assert_called_once() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_positive_spool_value(self, watch_callee): + main("--spool 2000".split()) + self.assertIn("spool", watch_callee.call_args[1]) + self.assertEqual(2000, watch_callee.call_args[1]["spool"]) + watch_callee.assert_called_once() + + watch_callee.reset_mock() + + main("--spool 20".split()) + self.assertIn("spool", watch_callee.call_args[1]) + self.assertEqual(20, watch_callee.call_args[1]["spool"]) + watch_callee.assert_called_once() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_cause_error_for_negative_spool_values(self, watch_callee): + self.assertEqual(2, main("--spool -1".split())) + watch_callee.assert_not_called() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_cause_error_for_invalid_spool_values(self, watch_callee): + self.assertEquals(2, main("--spool abc".split()), "Status code for not integer 'spool' argument should be 2") + self.assertEquals(2, main("--spool @".split()), "Status code for not integer 'spool' argument should be 2") + self.assertEquals(2, main("--spool []".split()), "Status code for not integer 'spool' argument should be 2") + From 8b649017d77986ae90b691aa2b4e9933e6e1c529 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 31 Dec 2017 13:54:05 -0200 Subject: [PATCH 06/93] Added 'spool' parameter validation and coverage tests, considering default value as 200 --- pytest_watch/tests/test_main.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index 9c04b9e..5396ac5 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -58,6 +58,13 @@ def test_positive_spool_value(self, watch_callee): self.assertEqual(20, watch_callee.call_args[1]["spool"]) watch_callee.assert_called_once() + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_default_spool_value(self, watch_callee): + main([]) + self.assertIn("spool", watch_callee.call_args[1]) + self.assertEqual(200, watch_callee.call_args[1]["spool"]) + watch_callee.assert_called_once() + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_cause_error_for_negative_spool_values(self, watch_callee): self.assertEqual(2, main("--spool -1".split())) From 6eab020769f4ff26c3e76bc5d201fe053e1d27ca Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 31 Dec 2017 13:59:38 -0200 Subject: [PATCH 07/93] Fix README --spool default docopt description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5987cb..7b388df 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ Options: --pdb Start the interactive Python debugger on errors. This also enables --wait to prevent pdb interruption. --spool Re-run after a delay (in milliseconds), allowing for - more file system events to queue up (default: 200 ms). + more file system events to queue up [default: 200]. -p --poll Use polling instead of OS events (useful in VMs). -v --verbose Increase verbosity of the output. -q --quiet Decrease verbosity of the output (precedence over -v). From f07934a7dcb45b4306393bdf3d531833eb0701bf Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 31 Dec 2017 17:36:43 -0200 Subject: [PATCH 08/93] git ignore profiling files (*.prof) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f5ffc20..b364795 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ Thumbs.db .coverage .coverage.* venv +*.prof From 6c1400b58c3f3e69219ca5198ee0a073016682de Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 31 Dec 2017 17:37:31 -0200 Subject: [PATCH 09/93] More detailed error message for --spool parameter --- pytest_watch/command.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytest_watch/command.py b/pytest_watch/command.py index 89c3c12..f2a3a05 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -103,11 +103,11 @@ def main(argv=None): try: spool = int(spool) except ValueError: - sys.stderr.write('Error: Spool must be an integer.\n') + sys.stderr.write('Error: Spool (--spool %s) must be an integer.\n' % spool) return 2 if spool < 0: - sys.stderr.write("Error: Spool value must be positive integer") + sys.stderr.write("Error: Spool value(--spool %s) must be positive integer" % spool) return 2 # Run pytest and watch for changes From efebe20376084692d66ea99a0a9af8802c77103d Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Tue, 2 Jan 2018 19:52:10 -0200 Subject: [PATCH 10/93] Refactor basic tests with default arguments. Added a new test for test_default_command --- pytest_watch/command.py | 6 ++-- pytest_watch/tests/test_main.py | 62 +++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/pytest_watch/command.py b/pytest_watch/command.py index f2a3a05..f86c864 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -11,7 +11,7 @@ --ignore Ignore directory from being watched and during collection (multi-allowed). --ext Comma-separated list of file extensions that can - trigger a new test run when changed (default: .py). + trigger a new test run when changed [default: .py]. Use --ext=* to allow any file (including .pyc). --config Load configuration from `file` instead of trying to locate one of the implicit configuration files. @@ -44,7 +44,7 @@ from . import __version__ from .config import merge_config -from .constants import ALL_EXTENSIONS +from .constants import ALL_EXTENSIONS, DEFAULT_EXTENSIONS from .watcher import watch @@ -96,7 +96,7 @@ def main(argv=None): extensions = [('.' if not e.startswith('.') else '') + e for e in args['--ext'].split(',')] else: - extensions = None + extensions = DEFAULT_EXTENSIONS # Parse numeric arguments spool = args['--spool'] diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index 5396ac5..dc3e7e4 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -1,41 +1,54 @@ +import sys import unittest - -from pytest_watch.command import main +import shutil +import tempfile try: from unittest.mock import patch except ImportError: from mock import patch +from pytest_watch.command import main + class TestCLIArguments(unittest.TestCase): + def _get_default_args(self): + return dict( + directories=[], + ignore=[], + extensions=[".py"], + beep_on_failure=True, + auto_clear=False, + wait=False, + beforerun=None, + afterrun=None, + onpass=None, + onfail=None, + onexit=None, + runner=None, + spool=200, + poll=False, + verbose=False, + quiet=False, + pytest_args=[] + ) + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_default_parameters(self, watch_callee): - default_args = dict( - directories=[], - ignore=[], - extensions=None, - beep_on_failure=True, - auto_clear=False, - wait=False, - beforerun=None, - afterrun=None, - onpass=None, - onfail=None, - onexit=None, - runner=None, - spool=200, - poll=False, - verbose=False, - quiet=False, - pytest_args=[] - ) - main([]) watch_callee.assert_called_once() - watch_callee.assert_called_once_with(**default_args) + watch_callee.assert_called_once_with(**self._get_default_args()) + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_empty_argv(self, watch_callee): + sys.argv[1:] = [] + + main() + + watch_callee.assert_called_once() + watch_callee.assert_called_once_with(**self._get_default_args()) @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_zero_spool_value(self, watch_callee): @@ -47,6 +60,7 @@ def test_zero_spool_value(self, watch_callee): @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_positive_spool_value(self, watch_callee): main("--spool 2000".split()) + self.assertIn("spool", watch_callee.call_args[1]) self.assertEqual(2000, watch_callee.call_args[1]["spool"]) watch_callee.assert_called_once() @@ -54,6 +68,7 @@ def test_positive_spool_value(self, watch_callee): watch_callee.reset_mock() main("--spool 20".split()) + self.assertIn("spool", watch_callee.call_args[1]) self.assertEqual(20, watch_callee.call_args[1]["spool"]) watch_callee.assert_called_once() @@ -61,6 +76,7 @@ def test_positive_spool_value(self, watch_callee): @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_default_spool_value(self, watch_callee): main([]) + self.assertIn("spool", watch_callee.call_args[1]) self.assertEqual(200, watch_callee.call_args[1]["spool"]) watch_callee.assert_called_once() From a87a3829d202229d3d2d595dfa412999cf9d4a89 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Tue, 2 Jan 2018 19:54:53 -0200 Subject: [PATCH 11/93] Small PEP8 adjust Move 'watch' lists default arguments to None and in-method declaration --- pytest_watch/__main__.py | 11 ++++++----- pytest_watch/command.py | 2 ++ pytest_watch/tests/test_main.py | 12 +++++++++--- pytest_watch/watcher.py | 10 ++++++++-- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/pytest_watch/__main__.py b/pytest_watch/__main__.py index 0fc492e..bacd9e7 100644 --- a/pytest_watch/__main__.py +++ b/pytest_watch/__main__.py @@ -8,12 +8,13 @@ :license: MIT, see LICENSE for more details. """ -import os -import sys - -if __name__ == '__main__': +def run_cli(): + import os + import sys sys.path.append(os.path.dirname(__file__)) from pytest_watch.command import main - main() + main(argv=sys.argv[1:]) + +if __name__ == '__main__': run_cli() diff --git a/pytest_watch/command.py b/pytest_watch/command.py index f86c864..b87acb6 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -68,6 +68,8 @@ def main(argv=None): # Get paths and initial pytest arguments directories = args[''] pytest_args = list(directories) + + # Merge pytest arguments and directories if '--' in directories: index = directories.index('--') directories = directories[:index] diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index dc3e7e4..cb7c96c 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -88,7 +88,13 @@ def test_cause_error_for_negative_spool_values(self, watch_callee): @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_cause_error_for_invalid_spool_values(self, watch_callee): - self.assertEquals(2, main("--spool abc".split()), "Status code for not integer 'spool' argument should be 2") - self.assertEquals(2, main("--spool @".split()), "Status code for not integer 'spool' argument should be 2") - self.assertEquals(2, main("--spool []".split()), "Status code for not integer 'spool' argument should be 2") + self.assertEquals(2, main("--spool abc".split()), + "Status code for not integer 'spool' " \ + "argument should be 2") + self.assertEquals(2, main("--spool @".split()), + "Status code for not integer 'spool' " \ + "argument should be 2") + self.assertEquals(2, main("--spool []".split()), + "Status code for not integer 'spool' " \ + "argument should be 2") diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index 3bc6d62..9396c40 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -184,10 +184,16 @@ def run_hook(cmd, *args): subprocess.call(command, shell=True) -def watch(directories=[], ignore=[], extensions=[], beep_on_failure=True, +def watch(directories=None, ignore=None, extensions=None, beep_on_failure=True, auto_clear=False, wait=False, beforerun=None, afterrun=None, onpass=None, onfail=None, onexit=None, runner=None, spool=None, - poll=False, verbose=False, quiet=False, pytest_args=[]): + poll=False, verbose=False, quiet=False, pytest_args=None): + + directories = [] if directories is None else directories + ignore = [] if ignore is None else ignore + extensions = [] if extensions is None else extensions + pytest_args = [] if pytest_args is None else pytest_args + argv = _get_pytest_runner(runner) + (pytest_args or []) if not directories: From 785a6453c6c0fb153d71370e02631b8206df693b Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Tue, 2 Jan 2018 19:56:52 -0200 Subject: [PATCH 12/93] Initial --ext, and command line parsing tests --- pytest_watch/tests/test_main.py | 194 ++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index cb7c96c..214394c 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -98,3 +98,197 @@ def test_cause_error_for_invalid_spool_values(self, watch_callee): "Status code for not integer 'spool' " \ "argument should be 2") + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_default_extensions(self, watch_callee): + main([]) + self.assertIn("extensions", watch_callee.call_args[1]) + self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_single_without_dot_extensions(self, watch_callee): + main("--ext py".split()) + self.assertIn("extensions", watch_callee.call_args[1]) + self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_single_with_dot_extensions(self, watch_callee): + main("--ext .py".split()) + self.assertIn("extensions", watch_callee.call_args[1]) + self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_multiple_extensions(self, watch_callee): + main("--ext .py,.html".split()) + self.assertIn("extensions", watch_callee.call_args[1]) + self.assertListEqual([".py", ".html"], watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_multiple_with_and_without_dots_extensions(self, watch_callee): + main("--ext .py,html".split()) + self.assertIn("extensions", watch_callee.call_args[1]) + self.assertListEqual([".py", ".html"], watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() + + watch_callee.reset_mock() + + main("--ext py,.html".split()) + self.assertIn("extensions", watch_callee.call_args[1]) + self.assertListEqual([".py", ".html"], watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() + + +class TestDirectoriesAndPytestArgsArgumentsSplit(unittest.TestCase): + + def setUp(self): + self.root_tmp = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.root_tmp) + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_no_directory_empty_pytest_arg(self, watch_callee): + main(["--"]) + + self.assertIn("pytest_args", watch_callee.call_args[1]) + self.assertListEqual([], watch_callee.call_args[1]["pytest_args"]) + watch_callee.assert_called_once() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_no_directory_single_pytest_arg(self, watch_callee): + main("-- --pdb".split()) + + self.assertIn("pytest_args", watch_callee.call_args[1]) + self.assertListEqual(["--pdb"], watch_callee.call_args[1]["pytest_args"]) + watch_callee.assert_called_once() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_no_directory_multiple_pytest_args(self, watch_callee): + main("-- --pdb --cov=.".split()) + + self.assertIn("pytest_args", watch_callee.call_args[1]) + self.assertListEqual(["--pdb", "--cov=."], watch_callee.call_args[1]["pytest_args"]) + watch_callee.assert_called_once() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_multiple_directory_no_pytest_args(self, watch_callee): + directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(2)] + + directories.append("--") + main(directories) + + self.assertIn("pytest_args", watch_callee.call_args[1]) + self.assertIn("directories", watch_callee.call_args[1]) + + fetched_pytest_args = watch_callee.call_args[1]["pytest_args"] + fetched_directories = watch_callee.call_args[1]["directories"] + + self.assertListEqual(directories[:-1], fetched_directories) + + self.assertGreater(len(fetched_pytest_args), 1) + self.assertEqual(len(fetched_pytest_args), len(fetched_directories)) + self.assertListEqual(fetched_directories, fetched_pytest_args) + watch_callee.assert_called_once() + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_single_directory_no_pytest_args(self, watch_callee): + main([self.root_tmp, "--"]) + + self.assertIn("pytest_args", watch_callee.call_args[1]) + pytest_args = watch_callee.call_args[1]["pytest_args"] + self.assertGreater(len(pytest_args), 0) + self.assertListEqual([self.root_tmp], pytest_args) + watch_callee.assert_called_once() + + fetched_directories = watch_callee.call_args[1]["directories"] + self.assertListEqual([self.root_tmp], fetched_directories) + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_single_directory_single_pytest_args(self, watch_callee): + vargs = [self.root_tmp, "--", "--pdb"] + main(vargs) + + self.assertIn("pytest_args", watch_callee.call_args[1]) + self.assertIn("directories", watch_callee.call_args[1]) + + fetched_pytest_args = watch_callee.call_args[1]["pytest_args"] + fetched_directories = watch_callee.call_args[1]["directories"] + + self.assertListEqual([vargs[0]], fetched_directories) + + pytest_args = watch_callee.call_args[1]["pytest_args"] + self.assertGreater(len(pytest_args), 0) + self.assertListEqual([self.root_tmp, "--pdb"], pytest_args) + watch_callee.assert_called_once() + + self.assertListEqual([self.root_tmp], fetched_directories) + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_single_directory_multiple_pytest_args(self, watch_callee): + vargs = [self.root_tmp, "--", "--pdb", "--cov=."] + main(vargs) + + self.assertIn("pytest_args", watch_callee.call_args[1]) + self.assertIn("directories", watch_callee.call_args[1]) + + fetched_pytest_args = watch_callee.call_args[1]["pytest_args"] + fetched_directories = watch_callee.call_args[1]["directories"] + + self.assertListEqual([vargs[0]], fetched_directories) + + pytest_args = watch_callee.call_args[1]["pytest_args"] + self.assertGreater(len(pytest_args), 0) + self.assertListEqual([self.root_tmp, "--pdb", "--cov=."], pytest_args) + watch_callee.assert_called_once() + + self.assertListEqual([self.root_tmp], fetched_directories) + + +class TestDirectoriesArguments(unittest.TestCase): + + def setUp(self): + self.root_tmp = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.root_tmp) + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def test_default_directories(self, watch_callee): + directories = [] + + main(directories) + + self.assertIn("directories", watch_callee.call_args[1]) + fetched_directories = watch_callee.call_args[1]["directories"] + self.assertListEqual(directories, fetched_directories) + watch_callee.assert_called_once() + + def test_single_directory(self): + directories = [self.root_tmp] + self._assert_directories(directories) + + def test_two_directory_values(self): + directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(2)] + self._assert_directories(directories) + + def test_hundred_directory_values(self): + directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(100)] + self._assert_directories(directories) + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) + def _assert_directories(self, directories, watch_callee=None): + self.assertGreater(len(directories), 0, "Testing multiple directories") + + main(directories) + + self.assertIn("directories", watch_callee.call_args[1]) + + fetched_directories = watch_callee.call_args[1]["directories"] + self.assertEqual(len(directories), len(fetched_directories)) + + self.assertListEqual(directories, fetched_directories) + watch_callee.assert_called_once() From 66c8c95e31d773a24506133adbb9cebca9410ee1 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Tue, 2 Jan 2018 20:38:40 -0200 Subject: [PATCH 13/93] Simplification of @patch declaration from method-level to class-level --- pytest_watch/tests/test_main.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index 214394c..0306734 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -50,6 +50,9 @@ def test_empty_argv(self, watch_callee): watch_callee.assert_called_once() watch_callee.assert_called_once_with(**self._get_default_args()) + +class TestSpoolArguments(unittest.TestCase): + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_zero_spool_value(self, watch_callee): main("--spool 0".split()) @@ -99,35 +102,33 @@ def test_cause_error_for_invalid_spool_values(self, watch_callee): "argument should be 2") - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +class TestExtensionsArguments(unittest.TestCase): + def test_default_extensions(self, watch_callee): main([]) self.assertIn("extensions", watch_callee.call_args[1]) self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_single_without_dot_extensions(self, watch_callee): main("--ext py".split()) self.assertIn("extensions", watch_callee.call_args[1]) self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_single_with_dot_extensions(self, watch_callee): main("--ext .py".split()) self.assertIn("extensions", watch_callee.call_args[1]) self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_multiple_extensions(self, watch_callee): main("--ext .py,.html".split()) self.assertIn("extensions", watch_callee.call_args[1]) self.assertListEqual([".py", ".html"], watch_callee.call_args[1]["extensions"]) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_multiple_with_and_without_dots_extensions(self, watch_callee): main("--ext .py,html".split()) self.assertIn("extensions", watch_callee.call_args[1]) @@ -142,6 +143,7 @@ def test_multiple_with_and_without_dots_extensions(self, watch_callee): watch_callee.assert_called_once() +@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) class TestDirectoriesAndPytestArgsArgumentsSplit(unittest.TestCase): def setUp(self): @@ -150,7 +152,6 @@ def setUp(self): def tearDown(self): shutil.rmtree(self.root_tmp) - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_no_directory_empty_pytest_arg(self, watch_callee): main(["--"]) @@ -158,7 +159,6 @@ def test_no_directory_empty_pytest_arg(self, watch_callee): self.assertListEqual([], watch_callee.call_args[1]["pytest_args"]) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_no_directory_single_pytest_arg(self, watch_callee): main("-- --pdb".split()) @@ -166,7 +166,6 @@ def test_no_directory_single_pytest_arg(self, watch_callee): self.assertListEqual(["--pdb"], watch_callee.call_args[1]["pytest_args"]) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_no_directory_multiple_pytest_args(self, watch_callee): main("-- --pdb --cov=.".split()) @@ -174,7 +173,6 @@ def test_no_directory_multiple_pytest_args(self, watch_callee): self.assertListEqual(["--pdb", "--cov=."], watch_callee.call_args[1]["pytest_args"]) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_multiple_directory_no_pytest_args(self, watch_callee): directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(2)] @@ -194,7 +192,6 @@ def test_multiple_directory_no_pytest_args(self, watch_callee): self.assertListEqual(fetched_directories, fetched_pytest_args) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_single_directory_no_pytest_args(self, watch_callee): main([self.root_tmp, "--"]) @@ -207,7 +204,6 @@ def test_single_directory_no_pytest_args(self, watch_callee): fetched_directories = watch_callee.call_args[1]["directories"] self.assertListEqual([self.root_tmp], fetched_directories) - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_single_directory_single_pytest_args(self, watch_callee): vargs = [self.root_tmp, "--", "--pdb"] main(vargs) @@ -227,7 +223,6 @@ def test_single_directory_single_pytest_args(self, watch_callee): self.assertListEqual([self.root_tmp], fetched_directories) - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_single_directory_multiple_pytest_args(self, watch_callee): vargs = [self.root_tmp, "--", "--pdb", "--cov=."] main(vargs) @@ -276,7 +271,7 @@ def test_two_directory_values(self): self._assert_directories(directories) def test_hundred_directory_values(self): - directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(100)] + directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(10)] self._assert_directories(directories) @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) From 6d9c39d026ad98ea43071d19d8b9ce31e5860f05 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Tue, 2 Jan 2018 23:38:33 -0200 Subject: [PATCH 14/93] Added tests for --ignore command line parameter. --- pytest_watch/tests/test_main.py | 75 ++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index 0306734..dcc1bd0 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -11,6 +11,7 @@ from pytest_watch.command import main +@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) class TestCLIArguments(unittest.TestCase): def _get_default_args(self): @@ -34,14 +35,12 @@ def _get_default_args(self): pytest_args=[] ) - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_default_parameters(self, watch_callee): main([]) watch_callee.assert_called_once() watch_callee.assert_called_once_with(**self._get_default_args()) - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_empty_argv(self, watch_callee): sys.argv[1:] = [] @@ -51,6 +50,78 @@ def test_empty_argv(self, watch_callee): watch_callee.assert_called_once_with(**self._get_default_args()) +@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +class TestIgnoreArgument(unittest.TestCase): + + def setUp(self): + self.root_tmp = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.root_tmp) + + def test_default_ignore_argument(self, watch_callee): + sys.argv[1:] = [] + + main() + + self.assertListEqual([], watch_callee.call_args[1]["ignore"]) + + self.assertNotIn("--ignore", + watch_callee.call_args[1]["pytest_args"]) + + def test_ignore_argument(self, watch_callee): + main(["--ignore", "pytest_watch"]) + + self.assertEqual(["pytest_watch"], + watch_callee.call_args[1]["ignore"]) + + self.assertIn("--ignore", + watch_callee.call_args[1]["pytest_args"]) + + def test_multiple_ignore_argument(self, watch_callee): + directories = [] + argv = [] + + for _ in range(2): + new_dir = tempfile.mkdtemp(dir=self.root_tmp) + argv.append("--ignore") + argv.append(new_dir) + directories.append(new_dir) + + main(argv) + + self.assertEqual(directories, + watch_callee.call_args[1]["ignore"]) + + pytest_args = watch_callee.call_args[1]["pytest_args"] + self.assertIn("--ignore", pytest_args) + ignore_idx = pytest_args.index("--ignore") + self.assertListEqual(argv, pytest_args) + + def test_multiple_ignore_argument_conflict(self, watch_callee): + directories = [] + argv = [] + + for _ in range(2): + new_dir = tempfile.mkdtemp(dir=self.root_tmp) + argv.append("--ignore") + argv.append(new_dir) + directories.append(new_dir) + + argv.append("--") + argv.append("--ignore") + argv.append(tempfile.mkdtemp(dir=self.root_tmp)) + + main(argv) + + self.assertEqual(directories, + watch_callee.call_args[1]["ignore"]) + + pytest_args = watch_callee.call_args[1]["pytest_args"] + self.assertIn("--ignore", pytest_args) + self.assertEqual(3, pytest_args.count("--ignore")) + + class TestSpoolArguments(unittest.TestCase): @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) From a243dd69341c5fbe6cb06e30f053a4f17d1d9bcb Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 3 Jan 2018 09:11:27 -0200 Subject: [PATCH 15/93] Added tests for --pdb and --wait command line parameters --- pytest_watch/tests/test_main.py | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index dcc1bd0..53837f0 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -50,6 +50,52 @@ def test_empty_argv(self, watch_callee): watch_callee.assert_called_once_with(**self._get_default_args()) +@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +class TestPdbArgument(unittest.TestCase): + + def test_default_pdb_argument(self, watch_callee): + sys.argv[1:] = [] + + main() + + self.assertFalse(watch_callee.call_args[1]["wait"]) + + self.assertNotIn("pdb", watch_callee.call_args[1]) + + self.assertNotIn("--pdb", + watch_callee.call_args[1]["pytest_args"]) + + def test_pdb_argument(self, watch_callee): + main(["--pdb"]) + + self.assertTrue(watch_callee.call_args[1]["wait"]) + + self.assertNotIn("pdb", watch_callee.call_args[1]) + + self.assertIn("--pdb", + watch_callee.call_args[1]["pytest_args"]) + + def test_pdb_and_wait_arguments(self, watch_callee): + main("--pdb --wait".split()) + + self.assertTrue(watch_callee.call_args[1]["wait"]) + + self.assertNotIn("pdb", watch_callee.call_args[1]) + + self.assertIn("--pdb", + watch_callee.call_args[1]["pytest_args"]) + + def test_pdb_off_and_wait_on_arguments(self, watch_callee): + main("--wait".split()) + + self.assertTrue(watch_callee.call_args[1]["wait"]) + + self.assertNotIn("pdb", watch_callee.call_args[1]) + + self.assertNotIn("--pdb", + watch_callee.call_args[1]["pytest_args"]) + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) class TestIgnoreArgument(unittest.TestCase): From 548e06a41f7367b4fe83c28b22a4b15e5bfc7ec4 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 3 Jan 2018 09:31:22 -0200 Subject: [PATCH 16/93] Added initial tests for --config command line parameter. pytest_watch.config.merge_config is mocked and not being tested in depth --- pytest_watch/tests/test_main.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index 53837f0..91662e6 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -96,6 +96,32 @@ def test_pdb_off_and_wait_on_arguments(self, watch_callee): watch_callee.call_args[1]["pytest_args"]) +@patch("pytest_watch.command.merge_config", side_effect=lambda *args, **kwargs: True) +@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +class TestConfigArgument(unittest.TestCase): + def test_default_config(self, watch_callee, merge_config_callee): + sys.argv[1:] = [] + + main() + + self.assertNotIn("config", watch_callee.call_args[1]) + self.assertNotIn("-c", watch_callee.call_args[1]["pytest_args"]) + + def test_config_argument(self, watch_callee, merge_config_callee): + self._assert_config_file(watch_callee, "pytest.ini") + watch_callee.reset_mock() + self._assert_config_file(watch_callee, "custom_config_file.txt") + + def _assert_config_file(self, watch_callee, filename): + main(["--config", filename]) + + self.assertNotIn("config", watch_callee.call_args[1]) + + pytest_args = watch_callee.call_args[1]["pytest_args"] + self.assertIn("-c", pytest_args) + self.assertEqual(filename, pytest_args[pytest_args.index("-c")+1]) + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) class TestIgnoreArgument(unittest.TestCase): From 1ad3d8a747f064cec9615f7f560e7b8da908de21 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 3 Jan 2018 09:33:05 -0200 Subject: [PATCH 17/93] README how to usage updated with fix on --ext [default: .py] definition --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b388df..db1b98f 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ Options: --ignore Ignore directory from being watched and during collection (multi-allowed). --ext Comma-separated list of file extensions that can - trigger a new test run when changed (default: .py). + trigger a new test run when changed [default: .py]. Use --ext=* to allow any file (including .pyc). --config Load configuration from `file` instead of trying to locate one of the implicit configuration files. From 117fe1dcddf016c73e8db97b907a746dfc70cf5c Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 3 Jan 2018 09:33:32 -0200 Subject: [PATCH 18/93] mock patch declaration moved to class-level. --- pytest_watch/tests/test_main.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index 91662e6..f0563fb 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -194,16 +194,15 @@ def test_multiple_ignore_argument_conflict(self, watch_callee): self.assertEqual(3, pytest_args.count("--ignore")) +@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) class TestSpoolArguments(unittest.TestCase): - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_zero_spool_value(self, watch_callee): main("--spool 0".split()) self.assertIn("spool", watch_callee.call_args[1]) self.assertEqual(0, watch_callee.call_args[1]["spool"]) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_positive_spool_value(self, watch_callee): main("--spool 2000".split()) @@ -219,7 +218,6 @@ def test_positive_spool_value(self, watch_callee): self.assertEqual(20, watch_callee.call_args[1]["spool"]) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_default_spool_value(self, watch_callee): main([]) @@ -227,12 +225,10 @@ def test_default_spool_value(self, watch_callee): self.assertEqual(200, watch_callee.call_args[1]["spool"]) watch_callee.assert_called_once() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_cause_error_for_negative_spool_values(self, watch_callee): self.assertEqual(2, main("--spool -1".split())) watch_callee.assert_not_called() - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_cause_error_for_invalid_spool_values(self, watch_callee): self.assertEquals(2, main("--spool abc".split()), "Status code for not integer 'spool' " \ From 1b6a0ead1d010c017926ab449a8e3d680e3dbe7f Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 3 Jan 2018 13:55:35 -0200 Subject: [PATCH 19/93] Add tests for --ext ALL_EXTENSIONS and default --ext value support. There is an unreachable code defining this after docopt syntex fix to .py --- pytest_watch/command.py | 4 +--- pytest_watch/tests/test_main.py | 9 +++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pytest_watch/command.py b/pytest_watch/command.py index b87acb6..dc7dbe8 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -91,14 +91,12 @@ def main(argv=None): if args['--pdb']: pytest_args.append('--pdb') - # Parse extensions + # Parse extensions [default: .py] if args['--ext'] == '*': extensions = ALL_EXTENSIONS elif args['--ext']: extensions = [('.' if not e.startswith('.') else '') + e for e in args['--ext'].split(',')] - else: - extensions = DEFAULT_EXTENSIONS # Parse numeric arguments spool = args['--spool'] diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index f0563fb..9210e50 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -9,6 +9,7 @@ from mock import patch from pytest_watch.command import main +from pytest_watch.constants import ALL_EXTENSIONS @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) @@ -250,6 +251,14 @@ def test_default_extensions(self, watch_callee): self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) watch_callee.assert_called_once() + def test_all_extensions(self, watch_callee): + main("--ext *".split()) + + self.assertEqual(object, type(watch_callee.call_args[1]["extensions"])) + self.assertIsNotNone(watch_callee.call_args[1]["extensions"]) + self.assertEqual(ALL_EXTENSIONS, watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() + def test_single_without_dot_extensions(self, watch_callee): main("--ext py".split()) self.assertIn("extensions", watch_callee.call_args[1]) From 897cf5ddc279a0026c1799ae4a68f9ac636ae7fe Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 3 Jan 2018 14:06:13 -0200 Subject: [PATCH 20/93] file moving from test_main.py -> test_command.py, according to described tests in the file --- pytest_watch/tests/{test_main.py => test_command.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pytest_watch/tests/{test_main.py => test_command.py} (100%) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_command.py similarity index 100% rename from pytest_watch/tests/test_main.py rename to pytest_watch/tests/test_command.py From b0f4b1894186690f67cb119e5fbced1dd5b7fdf2 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 3 Jan 2018 14:06:38 -0200 Subject: [PATCH 21/93] Added initial tests for __main__.py --- pytest_watch/__main__.py | 1 + pytest_watch/tests/test_main.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 pytest_watch/tests/test_main.py diff --git a/pytest_watch/__main__.py b/pytest_watch/__main__.py index bacd9e7..731bc4f 100644 --- a/pytest_watch/__main__.py +++ b/pytest_watch/__main__.py @@ -12,6 +12,7 @@ def run_cli(): import os import sys + sys.path.append(os.path.dirname(__file__)) from pytest_watch.command import main diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py new file mode 100644 index 0000000..09992b2 --- /dev/null +++ b/pytest_watch/tests/test_main.py @@ -0,0 +1,20 @@ +import os +import sys +import unittest + +try: + from unittest.mock import patch +except ImportError: + from mock import patch + +from pytest_watch import __main__ +from pytest_watch.__main__ import run_cli + + +class TestRunCLI(unittest.TestCase): + + @patch("pytest_watch.command.main", side_effect=lambda argv: 0) + def test_add_pytest_watch_folder_to_path(self, main): + run_cli() + self.assertIn(os.path.dirname(__main__.__file__), sys.path) + From d5e7710e7a1c32afb188bc3c70c45ba5fb145eab Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 00:01:54 -0200 Subject: [PATCH 22/93] Removed unused DEFAULT_EXTENSIONS constant. --- pytest_watch/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_watch/command.py b/pytest_watch/command.py index dc7dbe8..6719792 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -44,7 +44,7 @@ from . import __version__ from .config import merge_config -from .constants import ALL_EXTENSIONS, DEFAULT_EXTENSIONS +from .constants import ALL_EXTENSIONS from .watcher import watch From c673b9ac1f7ec8c06a71bb5b7fa19284b2670282 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 09:11:19 -0200 Subject: [PATCH 23/93] Replace explicit sys.argv[1:] declaration from main() call. As suggested in https://github.com/joeyespo/pytest-watch/pull/79#pullrequestreview-86549314, it was preferable to add more detailed docstring. --- pytest_watch/__main__.py | 2 +- pytest_watch/command.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pytest_watch/__main__.py b/pytest_watch/__main__.py index 731bc4f..53d0047 100644 --- a/pytest_watch/__main__.py +++ b/pytest_watch/__main__.py @@ -16,6 +16,6 @@ def run_cli(): sys.path.append(os.path.dirname(__file__)) from pytest_watch.command import main - main(argv=sys.argv[1:]) + main() if __name__ == '__main__': run_cli() diff --git a/pytest_watch/command.py b/pytest_watch/command.py index 6719792..586f350 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -55,6 +55,8 @@ def main(argv=None): """ The entry point of the application. + + argv -- List of strings to parse. The default is taken from sys.argv[1:]. """ if argv is None: argv = sys.argv[1:] From 0f8c89b2937b450a922d948f79a050de521b2e1b Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 13:41:11 -0200 Subject: [PATCH 24/93] PEP8 line length limits applied to stderr messages Messages formatting using .format() methods Tests over error messages for invalid --spool arguments Related to: https://github.com/joeyespo/pytest-watch/pull/79#pullrequestreview-86550675 --- pytest_watch/command.py | 7 +++-- pytest_watch/tests/test_command.py | 42 +++++++++++++++++++++--------- pytest_watch/tests/test_main.py | 5 ++-- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/pytest_watch/command.py b/pytest_watch/command.py index 586f350..e0a6edd 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -105,11 +105,14 @@ def main(argv=None): try: spool = int(spool) except ValueError: - sys.stderr.write('Error: Spool (--spool %s) must be an integer.\n' % spool) + sys.stderr.write('Error: Spool (--spool {}) must be an integer.\n' + .format(spool)) return 2 if spool < 0: - sys.stderr.write("Error: Spool value(--spool %s) must be positive integer" % spool) + sys.stderr.write('Error: Spool value(--spool {}) must be positive'\ + ' integer\n' + .format(spool)) return 2 # Run pytest and watch for changes diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index 9210e50..0a96d50 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -3,6 +3,11 @@ import shutil import tempfile +if sys.version_info[0] < 3: + from io import BytesIO as io_mock +else: + from io import StringIO as io_mock + try: from unittest.mock import patch except ImportError: @@ -97,7 +102,8 @@ def test_pdb_off_and_wait_on_arguments(self, watch_callee): watch_callee.call_args[1]["pytest_args"]) -@patch("pytest_watch.command.merge_config", side_effect=lambda *args, **kwargs: True) +@patch("pytest_watch.command.merge_config", + side_effect=lambda *args, **kwargs: True) @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) class TestConfigArgument(unittest.TestCase): def test_default_config(self, watch_callee, merge_config_callee): @@ -226,20 +232,32 @@ def test_default_spool_value(self, watch_callee): self.assertEqual(200, watch_callee.call_args[1]["spool"]) watch_callee.assert_called_once() - def test_cause_error_for_negative_spool_values(self, watch_callee): - self.assertEqual(2, main("--spool -1".split())) + def _assert_spool_error(self, watch_callee, value, err): + with patch("pytest_watch.command.sys.stderr", new=io_mock()) as out: + self.assertEqual(2, main(["--spool", value])) + assert err == out.getvalue(), \ + "Status code for invalid 'spool' argument should be 2" watch_callee.assert_not_called() + def test_cause_error_for_negative_spool_values(self, watch_callee): + err = "Error: Spool value(--spool -1) must be positive integer\n" + self._assert_spool_error(watch_callee, value="-1", err=err) + def test_cause_error_for_invalid_spool_values(self, watch_callee): - self.assertEquals(2, main("--spool abc".split()), - "Status code for not integer 'spool' " \ - "argument should be 2") - self.assertEquals(2, main("--spool @".split()), - "Status code for not integer 'spool' " \ - "argument should be 2") - self.assertEquals(2, main("--spool []".split()), - "Status code for not integer 'spool' " \ - "argument should be 2") + value = "abc" + self._assert_spool_error(watch_callee, value=value, + err="Error: Spool (--spool {}) must be" \ + " an integer.\n".format(value)) + + value = "@" + self._assert_spool_error(watch_callee, value=value, + err="Error: Spool (--spool {}) must be" \ + " an integer.\n".format(value)) + + value = "[]" + self._assert_spool_error(watch_callee, value=value, + err="Error: Spool (--spool {}) must be" \ + " an integer.\n".format(value)) @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index 09992b2..08e59c3 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -13,8 +13,7 @@ class TestRunCLI(unittest.TestCase): - @patch("pytest_watch.command.main", side_effect=lambda argv: 0) + @patch("pytest_watch.command.main", side_effect=lambda argv=None: 0) def test_add_pytest_watch_folder_to_path(self, main): run_cli() - self.assertIn(os.path.dirname(__main__.__file__), sys.path) - + assert os.path.dirname(__main__.__file__) in sys.path From d7828e9fc4608c1cfb3a09b3de81113c6edfb283 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 13:43:15 -0200 Subject: [PATCH 25/93] PEP8 line length applied to test_command.py --- pytest_watch/tests/test_command.py | 34 ++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index 0a96d50..d6929b5 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -272,9 +272,14 @@ def test_default_extensions(self, watch_callee): def test_all_extensions(self, watch_callee): main("--ext *".split()) - self.assertEqual(object, type(watch_callee.call_args[1]["extensions"])) + self.assertEqual(object, + type(watch_callee.call_args[1]["extensions"])) + self.assertIsNotNone(watch_callee.call_args[1]["extensions"]) - self.assertEqual(ALL_EXTENSIONS, watch_callee.call_args[1]["extensions"]) + + self.assertEqual(ALL_EXTENSIONS, + watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() def test_single_without_dot_extensions(self, watch_callee): @@ -292,20 +297,29 @@ def test_single_with_dot_extensions(self, watch_callee): def test_multiple_extensions(self, watch_callee): main("--ext .py,.html".split()) self.assertIn("extensions", watch_callee.call_args[1]) - self.assertListEqual([".py", ".html"], watch_callee.call_args[1]["extensions"]) + + self.assertListEqual([".py", ".html"], + watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() def test_multiple_with_and_without_dots_extensions(self, watch_callee): main("--ext .py,html".split()) self.assertIn("extensions", watch_callee.call_args[1]) - self.assertListEqual([".py", ".html"], watch_callee.call_args[1]["extensions"]) + + self.assertListEqual([".py", ".html"], + watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() watch_callee.reset_mock() main("--ext py,.html".split()) self.assertIn("extensions", watch_callee.call_args[1]) - self.assertListEqual([".py", ".html"], watch_callee.call_args[1]["extensions"]) + + self.assertListEqual([".py", ".html"], + watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() @@ -329,14 +343,20 @@ def test_no_directory_single_pytest_arg(self, watch_callee): main("-- --pdb".split()) self.assertIn("pytest_args", watch_callee.call_args[1]) - self.assertListEqual(["--pdb"], watch_callee.call_args[1]["pytest_args"]) + + self.assertListEqual(["--pdb"], + watch_callee.call_args[1]["pytest_args"]) + watch_callee.assert_called_once() def test_no_directory_multiple_pytest_args(self, watch_callee): main("-- --pdb --cov=.".split()) self.assertIn("pytest_args", watch_callee.call_args[1]) - self.assertListEqual(["--pdb", "--cov=."], watch_callee.call_args[1]["pytest_args"]) + + self.assertListEqual(["--pdb", "--cov=."], + watch_callee.call_args[1]["pytest_args"]) + watch_callee.assert_called_once() def test_multiple_directory_no_pytest_args(self, watch_callee): From 6bd4c342e309fdcb43b42ed1ea870b0cbb100288 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 14:48:10 -0200 Subject: [PATCH 26/93] Added requirements-test.txt file and declared it into setup.py --- requirements-test.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..ed93332 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1 @@ +pytest-cov>=2.5.1 diff --git a/setup.py b/setup.py index 2424673..52c388c 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,7 @@ def read(filename): platforms='any', packages=find_packages(), install_requires=read('requirements.txt').splitlines(), + tests_require=read('requirements-test.txt').splitlines(), entry_points={ 'console_scripts': [ 'pytest-watch = pytest_watch:main', From 26159fd6125b77269703675ced1aaeccc2a9c249 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 15:10:19 -0200 Subject: [PATCH 27/93] 'mock' lib is necessary for setup.py test under python2 environment. It was added to setup.py a verification for version and hardcoded inclusion of mock into test_requirements list. --- setup.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 52c388c..8dde742 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ import os +import sys from setuptools import setup, find_packages @@ -7,6 +8,15 @@ def read(filename): return f.read() +def get_test_requirements(): + reqs = read('requirements-test.txt').splitlines() + + if sys.version_info[0] < 3: + reqs.append("mock") + + return reqs + + setup( name='pytest-watch', version='4.1.0', @@ -19,7 +29,7 @@ def read(filename): platforms='any', packages=find_packages(), install_requires=read('requirements.txt').splitlines(), - tests_require=read('requirements-test.txt').splitlines(), + tests_require=get_test_requirements(), entry_points={ 'console_scripts': [ 'pytest-watch = pytest_watch:main', From 8a0d80d2a1e9bfac9899e5b6ba5b4876648b8d39 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 16:16:10 -0200 Subject: [PATCH 28/93] Replace verbose unittest for simple assertions to assert. Re: suggested by @joeyespo at https://github.com/joeyespo/pytest-watch/pull/79/files#r159580885 --- pytest_watch/tests/test_command.py | 162 ++++++++++++++++------------- 1 file changed, 88 insertions(+), 74 deletions(-) diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index d6929b5..59f9d1a 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -64,42 +64,38 @@ def test_default_pdb_argument(self, watch_callee): main() - self.assertFalse(watch_callee.call_args[1]["wait"]) + assert not watch_callee.call_args[1]["wait"] - self.assertNotIn("pdb", watch_callee.call_args[1]) + assert "pdb" not in watch_callee.call_args[1] - self.assertNotIn("--pdb", - watch_callee.call_args[1]["pytest_args"]) + assert "--pdb" not in watch_callee.call_args[1]["pytest_args"] def test_pdb_argument(self, watch_callee): main(["--pdb"]) - self.assertTrue(watch_callee.call_args[1]["wait"]) + assert watch_callee.call_args[1]["wait"] - self.assertNotIn("pdb", watch_callee.call_args[1]) + assert "pdb" not in watch_callee.call_args[1] - self.assertIn("--pdb", - watch_callee.call_args[1]["pytest_args"]) + assert "--pdb" in watch_callee.call_args[1]["pytest_args"] def test_pdb_and_wait_arguments(self, watch_callee): main("--pdb --wait".split()) - self.assertTrue(watch_callee.call_args[1]["wait"]) + assert watch_callee.call_args[1]["wait"] - self.assertNotIn("pdb", watch_callee.call_args[1]) + assert "pdb" not in watch_callee.call_args[1] - self.assertIn("--pdb", - watch_callee.call_args[1]["pytest_args"]) + assert "--pdb" in watch_callee.call_args[1]["pytest_args"] def test_pdb_off_and_wait_on_arguments(self, watch_callee): main("--wait".split()) - self.assertTrue(watch_callee.call_args[1]["wait"]) + assert watch_callee.call_args[1]["wait"] - self.assertNotIn("pdb", watch_callee.call_args[1]) + assert "pdb" not in watch_callee.call_args[1] - self.assertNotIn("--pdb", - watch_callee.call_args[1]["pytest_args"]) + assert "--pdb" not in watch_callee.call_args[1]["pytest_args"] @patch("pytest_watch.command.merge_config", @@ -111,8 +107,8 @@ def test_default_config(self, watch_callee, merge_config_callee): main() - self.assertNotIn("config", watch_callee.call_args[1]) - self.assertNotIn("-c", watch_callee.call_args[1]["pytest_args"]) + assert "config" not in watch_callee.call_args[1] + assert "-c" not in watch_callee.call_args[1]["pytest_args"] def test_config_argument(self, watch_callee, merge_config_callee): self._assert_config_file(watch_callee, "pytest.ini") @@ -122,11 +118,11 @@ def test_config_argument(self, watch_callee, merge_config_callee): def _assert_config_file(self, watch_callee, filename): main(["--config", filename]) - self.assertNotIn("config", watch_callee.call_args[1]) + assert "config" not in watch_callee.call_args[1] pytest_args = watch_callee.call_args[1]["pytest_args"] - self.assertIn("-c", pytest_args) - self.assertEqual(filename, pytest_args[pytest_args.index("-c")+1]) + assert "-c" in pytest_args + assert filename == pytest_args[pytest_args.index("-c")+1] @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) @@ -145,17 +141,14 @@ def test_default_ignore_argument(self, watch_callee): self.assertListEqual([], watch_callee.call_args[1]["ignore"]) - self.assertNotIn("--ignore", - watch_callee.call_args[1]["pytest_args"]) + assert "--ignore" not in watch_callee.call_args[1]["pytest_args"] def test_ignore_argument(self, watch_callee): main(["--ignore", "pytest_watch"]) - self.assertEqual(["pytest_watch"], - watch_callee.call_args[1]["ignore"]) + assert ["pytest_watch"] == watch_callee.call_args[1]["ignore"] - self.assertIn("--ignore", - watch_callee.call_args[1]["pytest_args"]) + assert "--ignore" in watch_callee.call_args[1]["pytest_args"] def test_multiple_ignore_argument(self, watch_callee): directories = [] @@ -169,11 +162,12 @@ def test_multiple_ignore_argument(self, watch_callee): main(argv) - self.assertEqual(directories, - watch_callee.call_args[1]["ignore"]) + assert directories == watch_callee.call_args[1]["ignore"] pytest_args = watch_callee.call_args[1]["pytest_args"] - self.assertIn("--ignore", pytest_args) + + assert "--ignore" in pytest_args + ignore_idx = pytest_args.index("--ignore") self.assertListEqual(argv, pytest_args) @@ -193,12 +187,11 @@ def test_multiple_ignore_argument_conflict(self, watch_callee): main(argv) - self.assertEqual(directories, - watch_callee.call_args[1]["ignore"]) + assert directories == watch_callee.call_args[1]["ignore"] pytest_args = watch_callee.call_args[1]["pytest_args"] - self.assertIn("--ignore", pytest_args) - self.assertEqual(3, pytest_args.count("--ignore")) + assert "--ignore" in pytest_args + assert 3 == pytest_args.count("--ignore") @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) @@ -206,35 +199,37 @@ class TestSpoolArguments(unittest.TestCase): def test_zero_spool_value(self, watch_callee): main("--spool 0".split()) - self.assertIn("spool", watch_callee.call_args[1]) - self.assertEqual(0, watch_callee.call_args[1]["spool"]) + + assert "spool" in watch_callee.call_args[1] + assert 0 == watch_callee.call_args[1]["spool"] + watch_callee.assert_called_once() def test_positive_spool_value(self, watch_callee): main("--spool 2000".split()) - self.assertIn("spool", watch_callee.call_args[1]) - self.assertEqual(2000, watch_callee.call_args[1]["spool"]) + assert "spool" in watch_callee.call_args[1] + assert 2000 == watch_callee.call_args[1]["spool"] watch_callee.assert_called_once() watch_callee.reset_mock() main("--spool 20".split()) - self.assertIn("spool", watch_callee.call_args[1]) - self.assertEqual(20, watch_callee.call_args[1]["spool"]) + assert "spool" in watch_callee.call_args[1] + assert 20 == watch_callee.call_args[1]["spool"] watch_callee.assert_called_once() def test_default_spool_value(self, watch_callee): main([]) - self.assertIn("spool", watch_callee.call_args[1]) - self.assertEqual(200, watch_callee.call_args[1]["spool"]) + assert "spool" in watch_callee.call_args[1] + assert 200 == watch_callee.call_args[1]["spool"] watch_callee.assert_called_once() def _assert_spool_error(self, watch_callee, value, err): with patch("pytest_watch.command.sys.stderr", new=io_mock()) as out: - self.assertEqual(2, main(["--spool", value])) + assert 2 == main(["--spool", value]) assert err == out.getvalue(), \ "Status code for invalid 'spool' argument should be 2" watch_callee.assert_not_called() @@ -265,38 +260,46 @@ class TestExtensionsArguments(unittest.TestCase): def test_default_extensions(self, watch_callee): main([]) - self.assertIn("extensions", watch_callee.call_args[1]) + + assert "extensions" in watch_callee.call_args[1] + self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() def test_all_extensions(self, watch_callee): main("--ext *".split()) - self.assertEqual(object, - type(watch_callee.call_args[1]["extensions"])) + assert object == type(watch_callee.call_args[1]["extensions"]) - self.assertIsNotNone(watch_callee.call_args[1]["extensions"]) + assert None != watch_callee.call_args[1]["extensions"] - self.assertEqual(ALL_EXTENSIONS, - watch_callee.call_args[1]["extensions"]) + assert ALL_EXTENSIONS == watch_callee.call_args[1]["extensions"] watch_callee.assert_called_once() def test_single_without_dot_extensions(self, watch_callee): main("--ext py".split()) - self.assertIn("extensions", watch_callee.call_args[1]) + + assert "extensions" in watch_callee.call_args[1] + self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() def test_single_with_dot_extensions(self, watch_callee): main("--ext .py".split()) - self.assertIn("extensions", watch_callee.call_args[1]) + + assert "extensions" in watch_callee.call_args[1] + self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) + watch_callee.assert_called_once() def test_multiple_extensions(self, watch_callee): main("--ext .py,.html".split()) - self.assertIn("extensions", watch_callee.call_args[1]) + + assert "extensions" in watch_callee.call_args[1] self.assertListEqual([".py", ".html"], watch_callee.call_args[1]["extensions"]) @@ -305,7 +308,8 @@ def test_multiple_extensions(self, watch_callee): def test_multiple_with_and_without_dots_extensions(self, watch_callee): main("--ext .py,html".split()) - self.assertIn("extensions", watch_callee.call_args[1]) + + assert "extensions" in watch_callee.call_args[1] self.assertListEqual([".py", ".html"], watch_callee.call_args[1]["extensions"]) @@ -315,7 +319,8 @@ def test_multiple_with_and_without_dots_extensions(self, watch_callee): watch_callee.reset_mock() main("--ext py,.html".split()) - self.assertIn("extensions", watch_callee.call_args[1]) + + assert "extensions" in watch_callee.call_args[1] self.assertListEqual([".py", ".html"], watch_callee.call_args[1]["extensions"]) @@ -335,14 +340,14 @@ def tearDown(self): def test_no_directory_empty_pytest_arg(self, watch_callee): main(["--"]) - self.assertIn("pytest_args", watch_callee.call_args[1]) + assert "pytest_args" in watch_callee.call_args[1] self.assertListEqual([], watch_callee.call_args[1]["pytest_args"]) watch_callee.assert_called_once() def test_no_directory_single_pytest_arg(self, watch_callee): main("-- --pdb".split()) - self.assertIn("pytest_args", watch_callee.call_args[1]) + assert "pytest_args" in watch_callee.call_args[1] self.assertListEqual(["--pdb"], watch_callee.call_args[1]["pytest_args"]) @@ -352,7 +357,7 @@ def test_no_directory_single_pytest_arg(self, watch_callee): def test_no_directory_multiple_pytest_args(self, watch_callee): main("-- --pdb --cov=.".split()) - self.assertIn("pytest_args", watch_callee.call_args[1]) + assert "pytest_args" in watch_callee.call_args[1] self.assertListEqual(["--pdb", "--cov=."], watch_callee.call_args[1]["pytest_args"]) @@ -361,29 +366,31 @@ def test_no_directory_multiple_pytest_args(self, watch_callee): def test_multiple_directory_no_pytest_args(self, watch_callee): directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(2)] - directories.append("--") + main(directories) - self.assertIn("pytest_args", watch_callee.call_args[1]) - self.assertIn("directories", watch_callee.call_args[1]) + assert "pytest_args" in watch_callee.call_args[1] + assert "directories" in watch_callee.call_args[1] fetched_pytest_args = watch_callee.call_args[1]["pytest_args"] fetched_directories = watch_callee.call_args[1]["directories"] self.assertListEqual(directories[:-1], fetched_directories) - self.assertGreater(len(fetched_pytest_args), 1) - self.assertEqual(len(fetched_pytest_args), len(fetched_directories)) + assert len(fetched_pytest_args) > 1 + assert len(fetched_pytest_args) == len(fetched_directories) self.assertListEqual(fetched_directories, fetched_pytest_args) watch_callee.assert_called_once() def test_single_directory_no_pytest_args(self, watch_callee): main([self.root_tmp, "--"]) - self.assertIn("pytest_args", watch_callee.call_args[1]) + assert "pytest_args" in watch_callee.call_args[1] + pytest_args = watch_callee.call_args[1]["pytest_args"] - self.assertGreater(len(pytest_args), 0) + assert len(pytest_args) > 0 + self.assertListEqual([self.root_tmp], pytest_args) watch_callee.assert_called_once() @@ -392,10 +399,11 @@ def test_single_directory_no_pytest_args(self, watch_callee): def test_single_directory_single_pytest_args(self, watch_callee): vargs = [self.root_tmp, "--", "--pdb"] + main(vargs) - self.assertIn("pytest_args", watch_callee.call_args[1]) - self.assertIn("directories", watch_callee.call_args[1]) + assert "pytest_args" in watch_callee.call_args[1] + assert "directories" in watch_callee.call_args[1] fetched_pytest_args = watch_callee.call_args[1]["pytest_args"] fetched_directories = watch_callee.call_args[1]["directories"] @@ -403,7 +411,7 @@ def test_single_directory_single_pytest_args(self, watch_callee): self.assertListEqual([vargs[0]], fetched_directories) pytest_args = watch_callee.call_args[1]["pytest_args"] - self.assertGreater(len(pytest_args), 0) + assert len(pytest_args) > 0 self.assertListEqual([self.root_tmp, "--pdb"], pytest_args) watch_callee.assert_called_once() @@ -411,10 +419,11 @@ def test_single_directory_single_pytest_args(self, watch_callee): def test_single_directory_multiple_pytest_args(self, watch_callee): vargs = [self.root_tmp, "--", "--pdb", "--cov=."] + main(vargs) - self.assertIn("pytest_args", watch_callee.call_args[1]) - self.assertIn("directories", watch_callee.call_args[1]) + assert "pytest_args" in watch_callee.call_args[1] + assert "directories" in watch_callee.call_args[1] fetched_pytest_args = watch_callee.call_args[1]["pytest_args"] fetched_directories = watch_callee.call_args[1]["directories"] @@ -422,8 +431,10 @@ def test_single_directory_multiple_pytest_args(self, watch_callee): self.assertListEqual([vargs[0]], fetched_directories) pytest_args = watch_callee.call_args[1]["pytest_args"] - self.assertGreater(len(pytest_args), 0) + assert len(pytest_args) > 0 + self.assertListEqual([self.root_tmp, "--pdb", "--cov=."], pytest_args) + watch_callee.assert_called_once() self.assertListEqual([self.root_tmp], fetched_directories) @@ -443,9 +454,11 @@ def test_default_directories(self, watch_callee): main(directories) - self.assertIn("directories", watch_callee.call_args[1]) + assert "directories" in watch_callee.call_args[1] + fetched_directories = watch_callee.call_args[1]["directories"] self.assertListEqual(directories, fetched_directories) + watch_callee.assert_called_once() def test_single_directory(self): @@ -462,14 +475,15 @@ def test_hundred_directory_values(self): @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def _assert_directories(self, directories, watch_callee=None): - self.assertGreater(len(directories), 0, "Testing multiple directories") + assert len(directories) > 0, \ + "Multiple directories should be declared for this test case" main(directories) - self.assertIn("directories", watch_callee.call_args[1]) + assert "directories" in watch_callee.call_args[1] fetched_directories = watch_callee.call_args[1]["directories"] - self.assertEqual(len(directories), len(fetched_directories)) + assert len(directories) == len(fetched_directories) self.assertListEqual(directories, fetched_directories) watch_callee.assert_called_once() From 309a8cf262c00d016c6d92b8ec84fcd95d4a3e35 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 16:19:31 -0200 Subject: [PATCH 29/93] classless tests over simple functions Re: suggested by @joeyespo at https://github.com/joeyespo/pytest-watch/pull/79/files#r159579312 --- pytest_watch/tests/test_main.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index 08e59c3..855809f 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -11,9 +11,7 @@ from pytest_watch.__main__ import run_cli -class TestRunCLI(unittest.TestCase): - - @patch("pytest_watch.command.main", side_effect=lambda argv=None: 0) - def test_add_pytest_watch_folder_to_path(self, main): - run_cli() - assert os.path.dirname(__main__.__file__) in sys.path +@patch("pytest_watch.command.main", side_effect=lambda argv=None: 0) +def test_add_pytest_watch_folder_to_path(main): + run_cli() + assert os.path.dirname(__main__.__file__) in sys.path From ef774dcca3bc446df1cf2f582837781469d3cc89 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 16:30:43 -0200 Subject: [PATCH 30/93] Add Travis-CI descriptor --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..852be43 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: python +python: + - "3.6.1" + +cache: pip + +install: + - pip install -r requirements.txt + - pip install -r requirements-tests.txt + +script: +- python -m pytest --cov=src/ src/ From 343ebc4005425c314e1ca25986e5e1f7d14ae7b8 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 16:36:44 -0200 Subject: [PATCH 31/93] Fix Travis requirements-test.txt declaration --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 852be43..94cf8f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ cache: pip install: - pip install -r requirements.txt - - pip install -r requirements-tests.txt + - pip install -r requirements-test.txt script: - python -m pytest --cov=src/ src/ From 636d2d918d28651b43f01be69c79da30dc3f2ce4 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 17:34:53 -0200 Subject: [PATCH 32/93] Fix travis.yml definition and add python 2.7.14 version to be tested --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 94cf8f0..adf5568 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,14 @@ language: python python: - "3.6.1" + - "2.7.14" cache: pip install: - pip install -r requirements.txt - pip install -r requirements-test.txt + - pip install mock script: -- python -m pytest --cov=src/ src/ +- python -m pytest --cov=pytest_watch pytest_watch From 4f9fc3204672278c405967efbcf0492cf7a2a267 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 17:55:03 -0200 Subject: [PATCH 33/93] Added OS list to Travis-CI descriptor: Linux, OSX, Windows --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index adf5568..339e8f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,10 @@ language: python python: - "3.6.1" - "2.7.14" +os: + - linux + - osx + - windows cache: pip From bb82b1de09609e8a1606125ac18616fcdd9d53aa Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 18:02:20 -0200 Subject: [PATCH 34/93] Travis-CI doesn't support Windows VM. It was removed from definitions Added conditional mock library installation. It will only installs if running python version 2* --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 339e8f0..1a45b76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,14 +5,13 @@ python: os: - linux - osx - - windows cache: pip install: - pip install -r requirements.txt - pip install -r requirements-test.txt - - pip install mock + - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install --allow-all-external mock; fi script: - python -m pytest --cov=pytest_watch pytest_watch From dd3cfbfef98534d18cfa49d3cffffa5182aa85aa Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 18:29:29 -0200 Subject: [PATCH 35/93] Travis-CI doesn't support python on OSX. This os was removed from travis project definitions --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1a45b76..1a25dbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ python: - "2.7.14" os: - linux - - osx cache: pip From 8c78d167f9d2c3b4b9225499aa8fbf3d691557c2 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 4 Jan 2018 21:37:43 -0200 Subject: [PATCH 36/93] _split_recursive changed to keep and return the same ignore directory list argument reference --- pytest_watch/tests/test_watcher.py | 12 ++++++++++++ pytest_watch/watcher.py | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 pytest_watch/tests/test_watcher.py diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py new file mode 100644 index 0000000..aab2207 --- /dev/null +++ b/pytest_watch/tests/test_watcher.py @@ -0,0 +1,12 @@ +import unittest + + +from pytest_watch.watcher import _split_recursive + + +class TestSplitRecursive(unittest.TestCase): + + def test_empty_split_recursive(self): + dirs = [] + ignore = [] + assert (dirs, ignore) == _split_recursive(dirs, ignore) diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index 9396c40..8e2f8f1 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -153,8 +153,10 @@ def _show_summary(argv, events, verbose=False): def _split_recursive(directories, ignore): + ignore = [] if ignore is None else ignore + if not ignore: - return directories, [] + return directories, ignore # TODO: Have this work recursively From a02236fad40f2d369859b9d39c23f8152461f0d3 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Fri, 5 Jan 2018 05:52:54 -0200 Subject: [PATCH 37/93] Move run_cli for its own line. Re: https://github.com/joeyespo/pytest-watch/pull/79#pullrequestreview-86813593 --- pytest_watch/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pytest_watch/__main__.py b/pytest_watch/__main__.py index 53d0047..cb2dd72 100644 --- a/pytest_watch/__main__.py +++ b/pytest_watch/__main__.py @@ -18,4 +18,6 @@ def run_cli(): from pytest_watch.command import main main() -if __name__ == '__main__': run_cli() + +if __name__ == '__main__': + run_cli() From 839acc7ab13feb2f82f3d43354369ead7000e476 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Fri, 5 Jan 2018 06:12:49 -0200 Subject: [PATCH 38/93] Asserting with the assert statement (without unittest assert* methods) Re: https://github.com/joeyespo/pytest-watch/pull/79#discussion_r159802695 --- pytest_watch/tests/test_command.py | 51 ++++++++++++++---------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index 59f9d1a..0979ecb 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -139,7 +139,7 @@ def test_default_ignore_argument(self, watch_callee): main() - self.assertListEqual([], watch_callee.call_args[1]["ignore"]) + assert [] == watch_callee.call_args[1]["ignore"] assert "--ignore" not in watch_callee.call_args[1]["pytest_args"] @@ -169,7 +169,7 @@ def test_multiple_ignore_argument(self, watch_callee): assert "--ignore" in pytest_args ignore_idx = pytest_args.index("--ignore") - self.assertListEqual(argv, pytest_args) + assert argv == pytest_args def test_multiple_ignore_argument_conflict(self, watch_callee): directories = [] @@ -263,7 +263,7 @@ def test_default_extensions(self, watch_callee): assert "extensions" in watch_callee.call_args[1] - self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) + assert [".py"] == watch_callee.call_args[1]["extensions"] watch_callee.assert_called_once() @@ -283,7 +283,7 @@ def test_single_without_dot_extensions(self, watch_callee): assert "extensions" in watch_callee.call_args[1] - self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) + assert [".py"] == watch_callee.call_args[1]["extensions"] watch_callee.assert_called_once() @@ -292,7 +292,7 @@ def test_single_with_dot_extensions(self, watch_callee): assert "extensions" in watch_callee.call_args[1] - self.assertListEqual([".py"], watch_callee.call_args[1]["extensions"]) + assert [".py"] == watch_callee.call_args[1]["extensions"] watch_callee.assert_called_once() @@ -301,8 +301,7 @@ def test_multiple_extensions(self, watch_callee): assert "extensions" in watch_callee.call_args[1] - self.assertListEqual([".py", ".html"], - watch_callee.call_args[1]["extensions"]) + assert [".py", ".html"] == watch_callee.call_args[1]["extensions"] watch_callee.assert_called_once() @@ -311,8 +310,7 @@ def test_multiple_with_and_without_dots_extensions(self, watch_callee): assert "extensions" in watch_callee.call_args[1] - self.assertListEqual([".py", ".html"], - watch_callee.call_args[1]["extensions"]) + assert [".py", ".html"] == watch_callee.call_args[1]["extensions"] watch_callee.assert_called_once() @@ -322,8 +320,7 @@ def test_multiple_with_and_without_dots_extensions(self, watch_callee): assert "extensions" in watch_callee.call_args[1] - self.assertListEqual([".py", ".html"], - watch_callee.call_args[1]["extensions"]) + assert [".py", ".html"] == watch_callee.call_args[1]["extensions"] watch_callee.assert_called_once() @@ -341,7 +338,7 @@ def test_no_directory_empty_pytest_arg(self, watch_callee): main(["--"]) assert "pytest_args" in watch_callee.call_args[1] - self.assertListEqual([], watch_callee.call_args[1]["pytest_args"]) + assert [] == watch_callee.call_args[1]["pytest_args"] watch_callee.assert_called_once() def test_no_directory_single_pytest_arg(self, watch_callee): @@ -349,8 +346,7 @@ def test_no_directory_single_pytest_arg(self, watch_callee): assert "pytest_args" in watch_callee.call_args[1] - self.assertListEqual(["--pdb"], - watch_callee.call_args[1]["pytest_args"]) + assert ["--pdb"] == watch_callee.call_args[1]["pytest_args"] watch_callee.assert_called_once() @@ -359,8 +355,7 @@ def test_no_directory_multiple_pytest_args(self, watch_callee): assert "pytest_args" in watch_callee.call_args[1] - self.assertListEqual(["--pdb", "--cov=."], - watch_callee.call_args[1]["pytest_args"]) + assert ["--pdb", "--cov=."] == watch_callee.call_args[1]["pytest_args"] watch_callee.assert_called_once() @@ -376,11 +371,11 @@ def test_multiple_directory_no_pytest_args(self, watch_callee): fetched_pytest_args = watch_callee.call_args[1]["pytest_args"] fetched_directories = watch_callee.call_args[1]["directories"] - self.assertListEqual(directories[:-1], fetched_directories) + assert directories[:-1] == fetched_directories assert len(fetched_pytest_args) > 1 assert len(fetched_pytest_args) == len(fetched_directories) - self.assertListEqual(fetched_directories, fetched_pytest_args) + assert fetched_directories == fetched_pytest_args watch_callee.assert_called_once() def test_single_directory_no_pytest_args(self, watch_callee): @@ -391,11 +386,11 @@ def test_single_directory_no_pytest_args(self, watch_callee): pytest_args = watch_callee.call_args[1]["pytest_args"] assert len(pytest_args) > 0 - self.assertListEqual([self.root_tmp], pytest_args) + assert [self.root_tmp] == pytest_args watch_callee.assert_called_once() fetched_directories = watch_callee.call_args[1]["directories"] - self.assertListEqual([self.root_tmp], fetched_directories) + assert [self.root_tmp] == fetched_directories def test_single_directory_single_pytest_args(self, watch_callee): vargs = [self.root_tmp, "--", "--pdb"] @@ -408,14 +403,14 @@ def test_single_directory_single_pytest_args(self, watch_callee): fetched_pytest_args = watch_callee.call_args[1]["pytest_args"] fetched_directories = watch_callee.call_args[1]["directories"] - self.assertListEqual([vargs[0]], fetched_directories) + assert [vargs[0]] == fetched_directories pytest_args = watch_callee.call_args[1]["pytest_args"] assert len(pytest_args) > 0 - self.assertListEqual([self.root_tmp, "--pdb"], pytest_args) + assert [self.root_tmp, "--pdb"] == pytest_args watch_callee.assert_called_once() - self.assertListEqual([self.root_tmp], fetched_directories) + assert [self.root_tmp] == fetched_directories def test_single_directory_multiple_pytest_args(self, watch_callee): vargs = [self.root_tmp, "--", "--pdb", "--cov=."] @@ -428,16 +423,16 @@ def test_single_directory_multiple_pytest_args(self, watch_callee): fetched_pytest_args = watch_callee.call_args[1]["pytest_args"] fetched_directories = watch_callee.call_args[1]["directories"] - self.assertListEqual([vargs[0]], fetched_directories) + assert [vargs[0]] == fetched_directories pytest_args = watch_callee.call_args[1]["pytest_args"] assert len(pytest_args) > 0 - self.assertListEqual([self.root_tmp, "--pdb", "--cov=."], pytest_args) + assert [self.root_tmp, "--pdb", "--cov=."] == pytest_args watch_callee.assert_called_once() - self.assertListEqual([self.root_tmp], fetched_directories) + assert [self.root_tmp] == fetched_directories class TestDirectoriesArguments(unittest.TestCase): @@ -457,7 +452,7 @@ def test_default_directories(self, watch_callee): assert "directories" in watch_callee.call_args[1] fetched_directories = watch_callee.call_args[1]["directories"] - self.assertListEqual(directories, fetched_directories) + assert directories == fetched_directories watch_callee.assert_called_once() @@ -485,5 +480,5 @@ def _assert_directories(self, directories, watch_callee=None): fetched_directories = watch_callee.call_args[1]["directories"] assert len(directories) == len(fetched_directories) - self.assertListEqual(directories, fetched_directories) + assert directories == fetched_directories watch_callee.assert_called_once() From 2fa423518384356981a5582813433595141eff92 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sat, 6 Jan 2018 14:17:39 -0200 Subject: [PATCH 39/93] Initial tests and small fix over pytest_watch.watcher._split_recursive There is a bug in _split_recursive function during ignore list building. It was cheking if only basename was a directory, not the full path prefixed by directory name . It was detected through unit tests over this function. Some comments were added to function body to describe what is happening step-by-step. Additional tests for recursive method support were implemented. This recursive functionality is not implemented yet. The tests were decorated by unittest.skip. --- pytest_watch/helpers.py | 2 +- pytest_watch/tests/test_watcher.py | 113 ++++++++++++++++++++++++++++- pytest_watch/watcher.py | 21 ++++-- 3 files changed, 129 insertions(+), 7 deletions(-) diff --git a/pytest_watch/helpers.py b/pytest_watch/helpers.py index 157565e..06b0d16 100644 --- a/pytest_watch/helpers.py +++ b/pytest_watch/helpers.py @@ -53,7 +53,7 @@ def dequeue_all(queue, spool=None): def samepath(left, right): """ - Determines whether two paths are the same. + Determines whether two paths are the same based on their absolute paths. """ return (os.path.abspath(os.path.normcase(left)) == os.path.abspath(os.path.normcase(right))) diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py index aab2207..1e1b362 100644 --- a/pytest_watch/tests/test_watcher.py +++ b/pytest_watch/tests/test_watcher.py @@ -1,12 +1,123 @@ +import os +import shutil +import tempfile import unittest from pytest_watch.watcher import _split_recursive -class TestSplitRecursive(unittest.TestCase): +class TestDirectoriesFiltering(unittest.TestCase): + + def setUp(self): + self.root_dir = tempfile.mkdtemp() + + def tearDown(self): + try: + shutil.rmtree(self.root_dir) + except: + pass def test_empty_split_recursive(self): dirs = [] ignore = [] assert (dirs, ignore) == _split_recursive(dirs, ignore) + + def test_non_empty_directories_empty_ignore(self): + dirs = ["."] + ignore = [] + + assert (dirs, ignore) == _split_recursive(dirs, ignore) + + def test_ignore_all_subdirs(self): + dirs = [self.root_dir] + + a_folder = tempfile.mkdtemp(dir=self.root_dir) + b_folder = tempfile.mkdtemp(dir=self.root_dir) + + ignore = [os.path.basename(a_folder), os.path.basename(b_folder)] + + assert ([], [self.root_dir]) == _split_recursive(dirs, ignore) + + def test_ignore_subdirs_partially(self): + """ + This test runs over the following tree structure: + self.root_dir + |_included_folder + |_excluded_folder + + Ignoring , the following behavior is expected: + . should be loaded non-recursivelly; + . and its children will be excluded; + . only will be loaded recursivelly. + """ + dirs = [self.root_dir] + + included_folder = tempfile.mkdtemp(dir=self.root_dir) + excluded_folder = tempfile.mkdtemp(dir=self.root_dir) + + ignore = [os.path.basename(excluded_folder)] + + assert ([included_folder], [self.root_dir]) == \ + _split_recursive(dirs, ignore), \ + "As folder {1} is ignored and is child of {0}, root {0} "\ + "folder should not be recursivelly observed. In this case, "\ + "folder {1} will be ignored, folder {2} should be "\ + "observed recursivelly and root "\ + "folder {0} should be not recursive."\ + .format(self.root_dir, excluded_folder, included_folder) + + @unittest.skip("Depends on pytest_watch.watcher._split_recursive support"\ + " for deep recursive navigation through directory tree") + def test_ignore_deep_subtree_multichild(self): + """ + This test runs over the following tree structure: + self.root_dir + |_tree_folder + ..|_subtree_folder_a + ....|_sub_subtree_folder + ..|_subtree_folder + ....|_sub_subtree_folder + + Ignoring , the following behavior is expected: + . should be loaded non-recursivelly; + . and its children will be excluded; + . only will be loaded recursivelly. + """ + dirs = [self.root_dir] + + tree_folder = tempfile.mkdtemp(dir=self.root_dir) + subtree_folder_a = tempfile.mkdtemp(dir=tree_folder) + subtree_folder = tempfile.mkdtemp(dir=tree_folder) + sub_subtree_folder = tempfile.mkdtemp(dir=tree_folder) + + ignore = [os.path.basename(subtree_folder)] + + assert ([self.root_dir], [tree_folder]) == \ + _split_recursive(dirs, ignore) + + @unittest.skip("Depends on pytest_watch.watcher._split_recursive support"\ + " for deep recursive navigation through directory tree") + def test_ignore_deep_subtree_single(self): + """ + This test runs over the following tree structure: + self.root_dir + |_tree_folder + ..|_subtree_folder + ....|_sub_subtree_folder + + Ignoring , the following behavior is expected: + . should be loaded non-recursivelly; + . and its children will be excluded; + . only will be loaded recursivelly. + """ + dirs = [self.root_dir] + + tree_folder = tempfile.mkdtemp(dir=self.root_dir) + subtree_folder = tempfile.mkdtemp(dir=tree_folder) + sub_subtree_folder = tempfile.mkdtemp(dir=tree_folder) + + ignore = [os.path.basename(subtree_folder)] + + assert ([self.root_dir], [tree_folder]) == \ + _split_recursive(dirs, ignore) diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index 8e2f8f1..7f77793 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -153,25 +153,36 @@ def _show_summary(argv, events, verbose=False): def _split_recursive(directories, ignore): - ignore = [] if ignore is None else ignore if not ignore: + # If ignore list is empty, all directories should be included. + # Return all + ignore = ignore if type(ignore) is list else [] return directories, ignore # TODO: Have this work recursively recursedirs, norecursedirs = [], [] for directory in directories: - subdirs = [os.path.join(directory, d) + # Build subdirectories paths list + join = os.path.join + subdirs = [join(directory, d) for d in os.listdir(directory) - if os.path.isdir(d)] + if os.path.isdir(join(directory, d))] + + # Filter not ignored subdirs in current folder filtered = [subdir for subdir in subdirs - if not any(samepath(os.path.join(directory, d), subdir) - for d in ignore)] + if not any(samepath(join(directory, ignore_name), subdir) + for ignore_name in ignore)] + if len(subdirs) == len(filtered): + # No subdirs were ignored recursedirs.append(directory) else: + # If any subdir is ignored, this folder will not be recursivelly + # observed norecursedirs.append(directory) + # But, non-ignored subdirs should be observed recursivelly recursedirs.extend(filtered) return sorted(set(recursedirs)), sorted(set(norecursedirs)) From b1c3197f18080f77d89d7d467c164623187af4f4 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sat, 6 Jan 2018 15:23:26 -0200 Subject: [PATCH 40/93] remove os.path.basename from ignore testes --- pytest_watch/tests/test_watcher.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py index 1e1b362..e66e9b9 100644 --- a/pytest_watch/tests/test_watcher.py +++ b/pytest_watch/tests/test_watcher.py @@ -3,7 +3,6 @@ import tempfile import unittest - from pytest_watch.watcher import _split_recursive @@ -35,7 +34,7 @@ def test_ignore_all_subdirs(self): a_folder = tempfile.mkdtemp(dir=self.root_dir) b_folder = tempfile.mkdtemp(dir=self.root_dir) - ignore = [os.path.basename(a_folder), os.path.basename(b_folder)] + ignore = [a_folder, b_folder] assert ([], [self.root_dir]) == _split_recursive(dirs, ignore) @@ -56,15 +55,14 @@ def test_ignore_subdirs_partially(self): included_folder = tempfile.mkdtemp(dir=self.root_dir) excluded_folder = tempfile.mkdtemp(dir=self.root_dir) - ignore = [os.path.basename(excluded_folder)] + ignore = [excluded_folder] assert ([included_folder], [self.root_dir]) == \ _split_recursive(dirs, ignore), \ - "As folder {1} is ignored and is child of {0}, root {0} "\ - "folder should not be recursivelly observed. In this case, "\ - "folder {1} will be ignored, folder {2} should be "\ - "observed recursivelly and root "\ - "folder {0} should be not recursive."\ + "Ignoring {1}, the following behavior is expected:\n"\ + ". {0} should be loaded non-recursivelly;\n"\ + ". {1} and its children will be excluded;\n"\ + ". only {2} will be loaded recursivelly.\n"\ .format(self.root_dir, excluded_folder, included_folder) @unittest.skip("Depends on pytest_watch.watcher._split_recursive support"\ @@ -91,7 +89,7 @@ def test_ignore_deep_subtree_multichild(self): subtree_folder = tempfile.mkdtemp(dir=tree_folder) sub_subtree_folder = tempfile.mkdtemp(dir=tree_folder) - ignore = [os.path.basename(subtree_folder)] + ignore = [subtree_folder] assert ([self.root_dir], [tree_folder]) == \ _split_recursive(dirs, ignore) @@ -117,7 +115,7 @@ def test_ignore_deep_subtree_single(self): subtree_folder = tempfile.mkdtemp(dir=tree_folder) sub_subtree_folder = tempfile.mkdtemp(dir=tree_folder) - ignore = [os.path.basename(subtree_folder)] + ignore = [subtree_folder] assert ([self.root_dir], [tree_folder]) == \ _split_recursive(dirs, ignore) From 61414b37d7313a593b91159503f3d44f951ae0a2 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 7 Jan 2018 12:05:55 -0200 Subject: [PATCH 41/93] Move join declaration outside for loop --- pytest_watch/watcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index 7f77793..72e5651 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -163,9 +163,9 @@ def _split_recursive(directories, ignore): # TODO: Have this work recursively recursedirs, norecursedirs = [], [] + join = os.path.join for directory in directories: # Build subdirectories paths list - join = os.path.join subdirs = [join(directory, d) for d in os.listdir(directory) if os.path.isdir(join(directory, d))] From dcfbe36ff5ff5558aaaa213156001330cf218ce3 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 7 Jan 2018 18:49:58 -0200 Subject: [PATCH 42/93] Added tests for hooks. Initially, beforerun and afterrun with KeyboardInterruption were covered by tests. subprocess.call was mocked with a wrapper with assertion support. Additional tests were added, but unittest.skip'ped. Next steps will cover their implementation. --- pytest_watch/tests/test_watch_hooks.py | 110 +++++++++++++++++++++++++ pytest_watch/tests/test_watcher.py | 9 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 pytest_watch/tests/test_watch_hooks.py diff --git a/pytest_watch/tests/test_watch_hooks.py b/pytest_watch/tests/test_watch_hooks.py new file mode 100644 index 0000000..87c3c7a --- /dev/null +++ b/pytest_watch/tests/test_watch_hooks.py @@ -0,0 +1,110 @@ +import subprocess +from subprocess import call as _subcall +import sys +import unittest + +try: + from unittest.mock import patch +except ImportError: + from mock import patch + +from pytest_watch.watcher import watch, run_hook + + +def assertion_wrapper(expected, callee, message=None): + """ + Adapter to support assertions as side_effect for patched objects. + + TODO: This implementation can be more generalized for assertions, i.e. !=, + using lambdas. For the moment, its satisfies run_hook tests. + """ + def _wrapped(*args, **kwargs): + if message: + assert expected == callee(*args, **kwargs), message + else: + assert expected == callee(*args, **kwargs) + return _wrapped + + +class TestRunHooksBasic(unittest.TestCase): + + @patch("pytest_watch.watcher.subprocess.call", + side_effect=assertion_wrapper(0, _subcall)) + def test_run_hook_systemexit_0(self, call_mock): + python_exec = sys.executable + cmd_parts = [python_exec, "-c", "'exit(0)'"] + cmd = " ".join(cmd_parts) + run_hook(cmd) + call_mock.assert_called_once_with(cmd, shell=True) + + @patch("pytest_watch.watcher.subprocess.call", + side_effect=assertion_wrapper(1, _subcall)) + def test_run_hook_systemexit_not_0(self, call_mock): + python_exec = sys.executable + cmd_parts = [python_exec, "-c", "'exit(1)'"] + cmd = " ".join(cmd_parts) + run_hook(cmd) + call_mock.assert_called_once_with(cmd, shell=True) + + +@patch("pytest_watch.watcher.subprocess.Popen", autospec=subprocess.Popen) +@patch("pytest_watch.watcher.subprocess.call", + side_effect=assertion_wrapper(0, _subcall)) +class TestRunHookCallbacks(unittest.TestCase): + + def test_beforerun(self, call_mock, popen_mock): + def raise_keyboard_interrupt(): + raise KeyboardInterrupt + + popen_mock.poll = raise_keyboard_interrupt + + beforerun="python -c 'exit(0) #it is beforerun'" + + watch(beforerun=beforerun) + + assert 1 == call_mock.call_count, \ + "Only beforerun is expected to be called." + call_mock.assert_called_with(beforerun, shell=True) + + def test_afterrun_for_keyboard_interruption(self, call_mock, popen_mock): + # force keyboard interruption + def raise_keyboard_interrupt(): + raise KeyboardInterrupt + + popen_mock.poll = raise_keyboard_interrupt + + afterrun="python -c 'exit(0) #it is afterrun'" + + watch(afterrun=afterrun) + + assert 1 == call_mock.call_count, \ + "Only afterrun is expected to be called." + + assert call_mock.call_args[0][0].startswith(afterrun + " ") + assert "shell" in call_mock.call_args[1] + assert True == call_mock.call_args[1]["shell"] + + +@unittest.skip("baby steps") +class TestRunHooksSkiped(unittest.TestCase): + + def test_run_hook_with_args(self): + assert False, "Not yet implemented" + + def test_run_hook_without_args(self): + assert False, "Not yet implemented" + + def test_afterrun_on_keyboard_interruption(self): + assert False, "Not yet implemented." + + def test_afterrun_with_exit_code(self): + assert False, "Not yet implemented." + + def test_onpass(self): + assert False, "Not yet implemented." + + def test_onfail(self): + assert False, "Not yet implemented." + + def test_onexit(self): + assert False, "Not yet implemented." diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py index e66e9b9..2e1f224 100644 --- a/pytest_watch/tests/test_watcher.py +++ b/pytest_watch/tests/test_watcher.py @@ -3,7 +3,12 @@ import tempfile import unittest -from pytest_watch.watcher import _split_recursive +try: + from unittest.mock import patch +except ImportError: + from mock import patch + +from pytest_watch.watcher import _split_recursive, run_hook, watch class TestDirectoriesFiltering(unittest.TestCase): @@ -119,3 +124,5 @@ def test_ignore_deep_subtree_single(self): assert ([self.root_dir], [tree_folder]) == \ _split_recursive(dirs, ignore) + + From b9ac9f45870f10df658cc345cb378953316b2612 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Tue, 9 Jan 2018 23:10:16 -0200 Subject: [PATCH 43/93] Added initial tests wid subprocess.Popen without block a forked process from main test. For this initial implementation, subprocess.Popen was mocked with Mock.configure_mock setup. pytest_wath.watcher.watch function was covered by tests partially, only for beforerun and afterrun cases. Next steps should cover Event Listening, observers and other low-level features. --- pytest_watch/tests/test_command.py | 2 +- pytest_watch/tests/test_watch_hooks.py | 90 ++++++++++++++++++-------- pytest_watch/watcher.py | 2 +- 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index 0979ecb..3381502 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -464,7 +464,7 @@ def test_two_directory_values(self): directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(2)] self._assert_directories(directories) - def test_hundred_directory_values(self): + def test_ten_directory_values(self): directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(10)] self._assert_directories(directories) diff --git a/pytest_watch/tests/test_watch_hooks.py b/pytest_watch/tests/test_watch_hooks.py index 87c3c7a..0303cd7 100644 --- a/pytest_watch/tests/test_watch_hooks.py +++ b/pytest_watch/tests/test_watch_hooks.py @@ -4,13 +4,24 @@ import unittest try: - from unittest.mock import patch + from unittest import mock except ImportError: - from mock import patch + import mock from pytest_watch.watcher import watch, run_hook +def build_popen_mock(popen, config): + mockmock = mock.Mock() + mockmock.configure_mock(**config) + popen.return_value = mockmock + + +def raise_keyboard_interrupt(*args, **kwargs): + # force keyboard interruption + raise KeyboardInterrupt() + + def assertion_wrapper(expected, callee, message=None): """ Adapter to support assertions as side_effect for patched objects. @@ -28,7 +39,7 @@ def _wrapped(*args, **kwargs): class TestRunHooksBasic(unittest.TestCase): - @patch("pytest_watch.watcher.subprocess.call", + @mock.patch("pytest_watch.watcher.subprocess.call", side_effect=assertion_wrapper(0, _subcall)) def test_run_hook_systemexit_0(self, call_mock): python_exec = sys.executable @@ -37,7 +48,7 @@ def test_run_hook_systemexit_0(self, call_mock): run_hook(cmd) call_mock.assert_called_once_with(cmd, shell=True) - @patch("pytest_watch.watcher.subprocess.call", + @mock.patch("pytest_watch.watcher.subprocess.call", side_effect=assertion_wrapper(1, _subcall)) def test_run_hook_systemexit_not_0(self, call_mock): python_exec = sys.executable @@ -47,42 +58,69 @@ def test_run_hook_systemexit_not_0(self, call_mock): call_mock.assert_called_once_with(cmd, shell=True) -@patch("pytest_watch.watcher.subprocess.Popen", autospec=subprocess.Popen) -@patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(0, _subcall)) -class TestRunHookCallbacks(unittest.TestCase): +from pytest_watch.watcher import subprocess as wsubprocess - def test_beforerun(self, call_mock, popen_mock): - def raise_keyboard_interrupt(): - raise KeyboardInterrupt - popen_mock.poll = raise_keyboard_interrupt +class TestRunHookCallbacks(unittest.TestCase): + + @mock.patch.object(wsubprocess, "Popen") + @mock.patch("pytest_watch.watcher.subprocess.call", + side_effect=assertion_wrapper(0, _subcall)) + def test_with_beforerun(self, call_mock, popen_mock): + """ + Test if beforerun callback is called if it is passed as argument + """ + config = {"poll.side_effect": raise_keyboard_interrupt, + "wait.return_value": 0} + build_popen_mock(popen_mock, config) beforerun="python -c 'exit(0) #it is beforerun'" watch(beforerun=beforerun) - assert 1 == call_mock.call_count, \ - "Only beforerun is expected to be called." - call_mock.assert_called_with(beforerun, shell=True) + call_mock.assert_called_once_with(beforerun, shell=True) + + @mock.patch.object(wsubprocess, "Popen") + @mock.patch("pytest_watch.helpers.send_keyboard_interrupt") + @mock.patch("pytest_watch.watcher.subprocess.call", + side_effect=assertion_wrapper(0, _subcall)) + def test_afterrun_for_keyboard_interruption(self, call_mock, keyb_int, popen_mock): + popen_config = {"poll.side_effect": raise_keyboard_interrupt, + "wait.return_value": 0} + build_popen_mock(popen_mock, config) - def test_afterrun_for_keyboard_interruption(self, call_mock, popen_mock): - # force keyboard interruption - def raise_keyboard_interrupt(): - raise KeyboardInterrupt + afterrun="python -c 'exit(0) #it is afterrun'" + + watch(afterrun=afterrun, wait=True) + + keyb_int.assert_not_called() + + call_mock.assert_called_once() + + expected_cmd = afterrun + " 0" # should run with p.wait() arg - popen_mock.poll = raise_keyboard_interrupt + call_mock.assert_called_once_with(expected_cmd, shell=True) + + @mock.patch.object(wsubprocess, "Popen") + @mock.patch("pytest_watch.helpers.send_keyboard_interrupt") + @mock.patch("pytest_watch.watcher.subprocess.call", + side_effect=assertion_wrapper(0, _subcall)) + def test_afterrun_for_keyboard_interruption(self, call_mock, keyb_int, popen_mock): + config = {"poll.side_effect": lambda: 999} + build_popen_mock(popen_mock, config) afterrun="python -c 'exit(0) #it is afterrun'" - watch(afterrun=afterrun) + watch(afterrun=afterrun, wait=True) + + keyb_int.assert_not_called() + + call_mock.assert_called_once() + + expected_cmd = afterrun + " 999" # should run with exit_code arg - assert 1 == call_mock.call_count, \ - "Only afterrun is expected to be called." + call_mock.assert_called_once_with(expected_cmd, shell=True) - assert call_mock.call_args[0][0].startswith(afterrun + " ") - assert "shell" in call_mock.call_args[1] - assert True == call_mock.call_args[1]["shell"] @unittest.skip("baby steps") diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index 72e5651..084e2ef 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -45,7 +45,7 @@ class EventListener(FileSystemEventHandler): """ Listens for changes to files and re-runs tests after each change. """ - def __init__(self, extensions=[]): + def __init__(self, extensions=None): super(EventListener, self).__init__() self.event_queue = Queue() self.extensions = extensions or DEFAULT_EXTENSIONS From da4ee195c7b7d57f34a130d346f27504f678182d Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 10 Jan 2018 09:41:22 -0200 Subject: [PATCH 44/93] Initial tests for pytest_watch.watcher.EventListener --- pytest_watch/tests/test_watcher_events.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 pytest_watch/tests/test_watcher_events.py diff --git a/pytest_watch/tests/test_watcher_events.py b/pytest_watch/tests/test_watcher_events.py new file mode 100644 index 0000000..463e71a --- /dev/null +++ b/pytest_watch/tests/test_watcher_events.py @@ -0,0 +1,23 @@ +from watchdog.events import FileModifiedEvent, DirModifiedEvent + +from pytest_watch.watcher import EventListener + + +def test_unwatched_event(): + event = DirModifiedEvent("/tmp/file.py") + listener = EventListener() + + assert listener.event_queue.empty() + listener.on_any_event(event) + + assert listener.event_queue.empty() + + +def test_file_modified_event(): + event = FileModifiedEvent("/tmp/file.py") + listener = EventListener() + + assert listener.event_queue.empty() + listener.on_any_event(event) + + assert not listener.event_queue.empty() From ac44193f7ec9d19329b0342a26405759aac75579 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 10 Jan 2018 10:42:54 -0200 Subject: [PATCH 45/93] Add basic tests for watched events. --- pytest_watch/tests/test_watcher_events.py | 32 ++++++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/pytest_watch/tests/test_watcher_events.py b/pytest_watch/tests/test_watcher_events.py index 463e71a..05d0bde 100644 --- a/pytest_watch/tests/test_watcher_events.py +++ b/pytest_watch/tests/test_watcher_events.py @@ -1,23 +1,41 @@ -from watchdog.events import FileModifiedEvent, DirModifiedEvent +from watchdog.events import FileModifiedEvent, FileMovedEvent, FileCreatedEvent, \ + FileDeletedEvent, FileCreatedEvent, DirModifiedEvent, FileSystemEvent from pytest_watch.watcher import EventListener -def test_unwatched_event(): - event = DirModifiedEvent("/tmp/file.py") +def _assert_watched_filesystem_event(event): listener = EventListener() assert listener.event_queue.empty() listener.on_any_event(event) - assert listener.event_queue.empty() + assert not listener.event_queue.empty() -def test_file_modified_event(): - event = FileModifiedEvent("/tmp/file.py") +def _assert_unwatched_filesystem_event(event): listener = EventListener() assert listener.event_queue.empty() listener.on_any_event(event) - assert not listener.event_queue.empty() + assert listener.event_queue.empty() + + +def test_unwatched_event(): + _assert_unwatched_filesystem_event(FileSystemEvent("/tmp/file.py")) + _assert_unwatched_filesystem_event(DirModifiedEvent("/tmp/")) + + +def test_file_modify_event(): + _assert_watched_filesystem_event(FileModifiedEvent("/tmp/file.py")) + +def test_file_create_event(): + _assert_watched_filesystem_event(FileCreatedEvent("/tmp/file.py")) + +def test_file_move_event(): + _assert_watched_filesystem_event(FileMovedEvent("/tmp/file.py", "/tmp/file-new.py")) + +def test_file_delete_event(): + _assert_watched_filesystem_event(FileDeletedEvent("/tmp/file.py")) + From 24a78a9948b18c8c24d3579b0a4ebac98cbfa484 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 10 Jan 2018 13:25:15 -0200 Subject: [PATCH 46/93] Improvements over test_event_move_file, covering os.path.relpath call over dest_folder --- pytest_watch/tests/test_watcher_events.py | 27 +++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/pytest_watch/tests/test_watcher_events.py b/pytest_watch/tests/test_watcher_events.py index 05d0bde..1e6bb4b 100644 --- a/pytest_watch/tests/test_watcher_events.py +++ b/pytest_watch/tests/test_watcher_events.py @@ -1,3 +1,8 @@ +try: + from unittest import mock +except: + import mock + from watchdog.events import FileModifiedEvent, FileMovedEvent, FileCreatedEvent, \ FileDeletedEvent, FileCreatedEvent, DirModifiedEvent, FileSystemEvent @@ -30,12 +35,30 @@ def test_unwatched_event(): def test_file_modify_event(): _assert_watched_filesystem_event(FileModifiedEvent("/tmp/file.py")) + def test_file_create_event(): _assert_watched_filesystem_event(FileCreatedEvent("/tmp/file.py")) -def test_file_move_event(): - _assert_watched_filesystem_event(FileMovedEvent("/tmp/file.py", "/tmp/file-new.py")) def test_file_delete_event(): _assert_watched_filesystem_event(FileDeletedEvent("/tmp/file.py")) + +from pytest_watch.watcher import os as wos + + +@mock.patch.object(wos.path, "relpath") +def test_file_move_event(relpath): + relpath.side_effect = lambda *args, **kwargs: args[0] + src_path = "/tmp/file.py" + dest_path = "/tmp/file-new.py" + + _assert_watched_filesystem_event(FileMovedEvent(src_path, dest_path)) + + assert 2 == relpath.call_count, \ + "os.path.relpath should be called twice (src_path, dest_path)" + + relpath.assert_any_call(src_path) + relpath.assert_any_call(dest_path) + + From e514278940d20d70113541cb9979aab7389ca430 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Wed, 10 Jan 2018 13:36:06 -0200 Subject: [PATCH 47/93] standard name for test_watcher_* tests files --- pytest_watch/tests/{test_watch_hooks.py => test_watcher_hooks.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pytest_watch/tests/{test_watch_hooks.py => test_watcher_hooks.py} (100%) diff --git a/pytest_watch/tests/test_watch_hooks.py b/pytest_watch/tests/test_watcher_hooks.py similarity index 100% rename from pytest_watch/tests/test_watch_hooks.py rename to pytest_watch/tests/test_watcher_hooks.py From 135b7ded2fb82f6dbbbc9090a9c6b22e6d1a63b8 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 11 Jan 2018 00:33:05 -0200 Subject: [PATCH 48/93] Tests over EventListener for observer, not observed extensions. It was applied to all supported events, and refused non supported events. Tests for pytest executor chooser function. --- pytest_watch/tests/test_watcher.py | 36 ++++++++++++++++++- pytest_watch/tests/test_watcher_events.py | 43 ++++++++++++++++++++--- pytest_watch/watcher.py | 6 ++-- 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py index 2e1f224..21dbf3a 100644 --- a/pytest_watch/tests/test_watcher.py +++ b/pytest_watch/tests/test_watcher.py @@ -1,5 +1,6 @@ import os import shutil +import sys import tempfile import unittest @@ -8,7 +9,8 @@ except ImportError: from mock import patch -from pytest_watch.watcher import _split_recursive, run_hook, watch +from pytest_watch.watcher import _split_recursive, run_hook, watch,\ + _get_pytest_runner class TestDirectoriesFiltering(unittest.TestCase): @@ -126,3 +128,35 @@ def test_ignore_deep_subtree_single(self): _split_recursive(dirs, ignore) +class TestPytestRunner(unittest.TestCase): + DEFAULT_EXECUTABLE = [sys.executable, "-m", "pytest"] + + def setUp(self): + self.virtual_env = os.getenv("VIRTUAL_ENV") + if "VIRTUAL_ENV" in os.environ: + del os.environ['VIRTUAL_ENV'] + + def tearDown(self): + if self.virtual_env: + os.putenv("VIRTUAL_ENV", self.virtual_env) + + def test_default_sys_executable(self): + assert TestPytestRunner.DEFAULT_EXECUTABLE == _get_pytest_runner() + + def test_empty_string_returns_sys_executable(self): + assert TestPytestRunner.DEFAULT_EXECUTABLE == _get_pytest_runner("") + assert TestPytestRunner.DEFAULT_EXECUTABLE == _get_pytest_runner(" ") + assert TestPytestRunner.DEFAULT_EXECUTABLE == _get_pytest_runner(" "*80) + + def test_custom_sys_executable(self): + assert ["mypytest"] == _get_pytest_runner("mypytest") + assert ["mypytest", "runtest"] == _get_pytest_runner("mypytest runtest") + + def test_virtualenv_executable(self): + os.environ["VIRTUAL_ENV"] = "/tmp/venv" + + assert ["py.test"] == _get_pytest_runner() + + del os.environ["VIRTUAL_ENV"] + + assert TestPytestRunner.DEFAULT_EXECUTABLE == _get_pytest_runner() diff --git a/pytest_watch/tests/test_watcher_events.py b/pytest_watch/tests/test_watcher_events.py index 1e6bb4b..7365251 100644 --- a/pytest_watch/tests/test_watcher_events.py +++ b/pytest_watch/tests/test_watcher_events.py @@ -1,3 +1,5 @@ +import tempfile + try: from unittest import mock except: @@ -6,11 +8,12 @@ from watchdog.events import FileModifiedEvent, FileMovedEvent, FileCreatedEvent, \ FileDeletedEvent, FileCreatedEvent, DirModifiedEvent, FileSystemEvent +from pytest_watch.constants import ALL_EXTENSIONS from pytest_watch.watcher import EventListener -def _assert_watched_filesystem_event(event): - listener = EventListener() +def _assert_watched_filesystem_event(event, event_listener=None): + listener = event_listener if event_listener else EventListener() assert listener.event_queue.empty() listener.on_any_event(event) @@ -18,8 +21,8 @@ def _assert_watched_filesystem_event(event): assert not listener.event_queue.empty() -def _assert_unwatched_filesystem_event(event): - listener = EventListener() +def _assert_unwatched_filesystem_event(event, event_listener=None): + listener = event_listener if event_listener else EventListener() assert listener.event_queue.empty() listener.on_any_event(event) @@ -56,9 +59,39 @@ def test_file_move_event(relpath): _assert_watched_filesystem_event(FileMovedEvent(src_path, dest_path)) assert 2 == relpath.call_count, \ - "os.path.relpath should be called twice (src_path, dest_path)" + "os.path.relpath should be called twice when file is moved src,dst" relpath.assert_any_call(src_path) relpath.assert_any_call(dest_path) +import pytest +import shutil + + +@pytest.fixture() +def tmpdir(): + d = tempfile.mkdtemp() + yield d + shutil.rmtree(d) + + +def test_event_over_all_extesions(tmpdir): + _, filename = tempfile.mkstemp(prefix=tmpdir, suffix=".py") + event = FileCreatedEvent(filename) + listener = EventListener(extensions=ALL_EXTENSIONS) + _assert_watched_filesystem_event(event, event_listener=listener) + + +def test_event_over_observed_file(tmpdir): + _, filename = tempfile.mkstemp(prefix=tmpdir, suffix=".py") + event = FileCreatedEvent(filename) + listener = EventListener(extensions=[".py"]) + _assert_watched_filesystem_event(event, event_listener=listener) + + +def test_event_over_not_observed_file(tmpdir): + _, filename = tempfile.mkstemp(prefix=tmpdir, suffix=".pyc") + event = FileCreatedEvent(filename) + listener = EventListener(extensions=[".py"]) + _assert_unwatched_filesystem_event(event, event_listener=listener) diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index 084e2ef..b329295 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -78,11 +78,13 @@ def on_any_event(self, event): self.event_queue.put((type(event), src_path, dest_path)) -def _get_pytest_runner(custom): - if custom: +def _get_pytest_runner(custom=None): + if custom and custom.strip(): return custom.split(' ') + if os.getenv('VIRTUAL_ENV'): return ['py.test'] + return [sys.executable, '-m', 'pytest'] From 973b3b583d67db145894c82c6f8f3e9f12a96c83 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 11 Jan 2018 09:09:45 -0200 Subject: [PATCH 49/93] fix cumulative test folder create&destroy --- pytest_watch/tests/test_watcher_events.py | 59 +++++++++++------------ 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/pytest_watch/tests/test_watcher_events.py b/pytest_watch/tests/test_watcher_events.py index 7365251..219e3e3 100644 --- a/pytest_watch/tests/test_watcher_events.py +++ b/pytest_watch/tests/test_watcher_events.py @@ -1,4 +1,6 @@ +import shutil import tempfile +import unittest try: from unittest import mock @@ -65,33 +67,30 @@ def test_file_move_event(relpath): relpath.assert_any_call(dest_path) -import pytest -import shutil - - -@pytest.fixture() -def tmpdir(): - d = tempfile.mkdtemp() - yield d - shutil.rmtree(d) - - -def test_event_over_all_extesions(tmpdir): - _, filename = tempfile.mkstemp(prefix=tmpdir, suffix=".py") - event = FileCreatedEvent(filename) - listener = EventListener(extensions=ALL_EXTENSIONS) - _assert_watched_filesystem_event(event, event_listener=listener) - - -def test_event_over_observed_file(tmpdir): - _, filename = tempfile.mkstemp(prefix=tmpdir, suffix=".py") - event = FileCreatedEvent(filename) - listener = EventListener(extensions=[".py"]) - _assert_watched_filesystem_event(event, event_listener=listener) - - -def test_event_over_not_observed_file(tmpdir): - _, filename = tempfile.mkstemp(prefix=tmpdir, suffix=".pyc") - event = FileCreatedEvent(filename) - listener = EventListener(extensions=[".py"]) - _assert_unwatched_filesystem_event(event, event_listener=listener) +class TestExtensionsMatch(unittest.TestCase): + def setUp(self): + self.root_dir = tempfile.mkdtemp() + + def tearDown(self): + try: + shutil.rmtree(self.root_dir) + except: + pass + + def test_event_over_all_extesions(self): + _, filename = tempfile.mkstemp(dir=self.root_dir, suffix=".py") + event = FileCreatedEvent(filename) + listener = EventListener(extensions=ALL_EXTENSIONS) + _assert_watched_filesystem_event(event, event_listener=listener) + + def test_event_over_observed_file(self): + _, filename = tempfile.mkstemp(dir=self.root_dir, suffix=".py") + event = FileCreatedEvent(filename) + listener = EventListener(extensions=[".py"]) + _assert_watched_filesystem_event(event, event_listener=listener) + + def test_event_over_not_observed_file(self): + _, filename = tempfile.mkstemp(dir=self.root_dir, suffix=".pyc") + event = FileCreatedEvent(filename) + listener = EventListener(extensions=[".py"]) + _assert_unwatched_filesystem_event(event, event_listener=listener) From dc6a3267a4bc7f471d80bd0c45ed3c03bcccd3c6 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 11 Jan 2018 13:04:08 -0200 Subject: [PATCH 50/93] SImple comment. Next step will be group variables closest to its usage --- pytest_watch/watcher.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index b329295..b0220f8 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -211,6 +211,7 @@ def watch(directories=None, ignore=None, extensions=None, beep_on_failure=True, argv = _get_pytest_runner(runner) + (pytest_args or []) + # Prepare directories if not directories: directories = ['.'] directories = [os.path.abspath(directory) for directory in directories] From 2557cc2442247f8a94b08add4a8215621fed7c46 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 11 Jan 2018 14:06:14 -0200 Subject: [PATCH 51/93] Added tests for not existent directories. Following spec https://docs.python.org/3/library/exceptions.html#FileNotFoundError, exception changed to FileNotFoundError. --- pytest_watch/tests/test_watcher.py | 10 ++++++++++ pytest_watch/watcher.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py index 21dbf3a..9210b7f 100644 --- a/pytest_watch/tests/test_watcher.py +++ b/pytest_watch/tests/test_watcher.py @@ -35,6 +35,16 @@ def test_non_empty_directories_empty_ignore(self): assert (dirs, ignore) == _split_recursive(dirs, ignore) + def test_invalid_directories(self): + dirs = [self.root_dir] + + fake_dir = os.path.join(self.root_dir, "atrocadocapacausti") + + import pytest + + with pytest.raises(FileNotFoundError): + watch(directories=[fake_dir]) + def test_ignore_all_subdirs(self): dirs = [self.root_dir] diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index b0220f8..ec5597b 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -217,7 +217,7 @@ def watch(directories=None, ignore=None, extensions=None, beep_on_failure=True, directories = [os.path.abspath(directory) for directory in directories] for directory in directories: if not os.path.isdir(directory): - raise ValueError('Directory not found: ' + directory) + raise FileNotFoundError('Directory not found: ' + directory) # Setup event handler event_listener = EventListener(extensions) From b915f81e68e2852bb59de5fec0a43e2a3f635c23 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 11 Jan 2018 16:15:17 -0200 Subject: [PATCH 52/93] fix test_afterrun* methods conflicts --- pytest_watch/tests/test_watcher_hooks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pytest_watch/tests/test_watcher_hooks.py b/pytest_watch/tests/test_watcher_hooks.py index 0303cd7..f80c075 100644 --- a/pytest_watch/tests/test_watcher_hooks.py +++ b/pytest_watch/tests/test_watcher_hooks.py @@ -85,8 +85,8 @@ def test_with_beforerun(self, call_mock, popen_mock): @mock.patch("pytest_watch.watcher.subprocess.call", side_effect=assertion_wrapper(0, _subcall)) def test_afterrun_for_keyboard_interruption(self, call_mock, keyb_int, popen_mock): - popen_config = {"poll.side_effect": raise_keyboard_interrupt, - "wait.return_value": 0} + config = {"poll.side_effect": raise_keyboard_interrupt, + "wait.return_value": 10} build_popen_mock(popen_mock, config) afterrun="python -c 'exit(0) #it is afterrun'" @@ -97,7 +97,7 @@ def test_afterrun_for_keyboard_interruption(self, call_mock, keyb_int, popen_moc call_mock.assert_called_once() - expected_cmd = afterrun + " 0" # should run with p.wait() arg + expected_cmd = afterrun + " 10" # should run with p.wait() arg call_mock.assert_called_once_with(expected_cmd, shell=True) @@ -105,7 +105,7 @@ def test_afterrun_for_keyboard_interruption(self, call_mock, keyb_int, popen_moc @mock.patch("pytest_watch.helpers.send_keyboard_interrupt") @mock.patch("pytest_watch.watcher.subprocess.call", side_effect=assertion_wrapper(0, _subcall)) - def test_afterrun_for_keyboard_interruption(self, call_mock, keyb_int, popen_mock): + def test_afterrun_without_keyboard_interruption(self, call_mock, keyb_int, popen_mock): config = {"poll.side_effect": lambda: 999} build_popen_mock(popen_mock, config) From 158ca45995bde758a41001786c7994265faf66ce Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sat, 13 Jan 2018 20:02:50 -0200 Subject: [PATCH 53/93] Fix tests over afterrun and beforerun, removing redundant Popen calls and mocks. This code is not the best but is testing correctly now --- pytest_watch/tests/test_watcher_hooks.py | 25 +++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/pytest_watch/tests/test_watcher_hooks.py b/pytest_watch/tests/test_watcher_hooks.py index f80c075..99040bb 100644 --- a/pytest_watch/tests/test_watcher_hooks.py +++ b/pytest_watch/tests/test_watcher_hooks.py @@ -8,7 +8,9 @@ except ImportError: import mock + from pytest_watch.watcher import watch, run_hook +from pytest_watch.watcher import subprocess as wsubprocess def build_popen_mock(popen, config): @@ -58,14 +60,12 @@ def test_run_hook_systemexit_not_0(self, call_mock): call_mock.assert_called_once_with(cmd, shell=True) -from pytest_watch.watcher import subprocess as wsubprocess - class TestRunHookCallbacks(unittest.TestCase): @mock.patch.object(wsubprocess, "Popen") @mock.patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(0, _subcall)) + side_effect=assertion_wrapper(0, lambda *args, **kwargs: 0)) def test_with_beforerun(self, call_mock, popen_mock): """ Test if beforerun callback is called if it is passed as argument @@ -74,7 +74,7 @@ def test_with_beforerun(self, call_mock, popen_mock): "wait.return_value": 0} build_popen_mock(popen_mock, config) - beforerun="python -c 'exit(0) #it is beforerun'" + beforerun="{} -c 'exit(0) #it is beforerun'".format(sys.executable) watch(beforerun=beforerun) @@ -83,13 +83,13 @@ def test_with_beforerun(self, call_mock, popen_mock): @mock.patch.object(wsubprocess, "Popen") @mock.patch("pytest_watch.helpers.send_keyboard_interrupt") @mock.patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(0, _subcall)) + side_effect=assertion_wrapper(0, lambda *args, **kwargs: 0)) def test_afterrun_for_keyboard_interruption(self, call_mock, keyb_int, popen_mock): config = {"poll.side_effect": raise_keyboard_interrupt, "wait.return_value": 10} build_popen_mock(popen_mock, config) - afterrun="python -c 'exit(0) #it is afterrun'" + afterrun="{} -m this".format(sys.executable) watch(afterrun=afterrun, wait=True) @@ -104,12 +104,20 @@ def test_afterrun_for_keyboard_interruption(self, call_mock, keyb_int, popen_moc @mock.patch.object(wsubprocess, "Popen") @mock.patch("pytest_watch.helpers.send_keyboard_interrupt") @mock.patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(0, _subcall)) + side_effect=assertion_wrapper(0, lambda *args, **kwargs: 0)) def test_afterrun_without_keyboard_interruption(self, call_mock, keyb_int, popen_mock): config = {"poll.side_effect": lambda: 999} build_popen_mock(popen_mock, config) - afterrun="python -c 'exit(0) #it is afterrun'" + afterrun="{} -c 'exit(0) #it is afterrun'".format(sys.executable) + + from pytest_watch import watcher + orig = watcher.run_hook + def run_hook_wrapper(cmd, *args): + orig(cmd, *args) + if cmd == afterrun: + raise StopIteration("Force this only for tests purpose") + watcher.run_hook = run_hook_wrapper watch(afterrun=afterrun, wait=True) @@ -122,7 +130,6 @@ def test_afterrun_without_keyboard_interruption(self, call_mock, keyb_int, popen call_mock.assert_called_once_with(expected_cmd, shell=True) - @unittest.skip("baby steps") class TestRunHooksSkiped(unittest.TestCase): From 7e272aaa74f8f806b5a755e000c1ec3e2cdcfbed Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sat, 13 Jan 2018 22:29:52 -0200 Subject: [PATCH 54/93] refactor redundant test cases and method-level patch --- pytest_watch/tests/test_watcher_hooks.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/pytest_watch/tests/test_watcher_hooks.py b/pytest_watch/tests/test_watcher_hooks.py index 99040bb..e8aec33 100644 --- a/pytest_watch/tests/test_watcher_hooks.py +++ b/pytest_watch/tests/test_watcher_hooks.py @@ -61,11 +61,11 @@ def test_run_hook_systemexit_not_0(self, call_mock): +@mock.patch.object(wsubprocess, "Popen") +@mock.patch("pytest_watch.watcher.subprocess.call", + side_effect=lambda *args, **kwargs: 0) class TestRunHookCallbacks(unittest.TestCase): - @mock.patch.object(wsubprocess, "Popen") - @mock.patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(0, lambda *args, **kwargs: 0)) def test_with_beforerun(self, call_mock, popen_mock): """ Test if beforerun callback is called if it is passed as argument @@ -80,11 +80,8 @@ def test_with_beforerun(self, call_mock, popen_mock): call_mock.assert_called_once_with(beforerun, shell=True) - @mock.patch.object(wsubprocess, "Popen") @mock.patch("pytest_watch.helpers.send_keyboard_interrupt") - @mock.patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(0, lambda *args, **kwargs: 0)) - def test_afterrun_for_keyboard_interruption(self, call_mock, keyb_int, popen_mock): + def test_afterrun_on_keyboard_interruption(self, keyb_int, call_mock, popen_mock): config = {"poll.side_effect": raise_keyboard_interrupt, "wait.return_value": 10} build_popen_mock(popen_mock, config) @@ -101,11 +98,8 @@ def test_afterrun_for_keyboard_interruption(self, call_mock, keyb_int, popen_moc call_mock.assert_called_once_with(expected_cmd, shell=True) - @mock.patch.object(wsubprocess, "Popen") @mock.patch("pytest_watch.helpers.send_keyboard_interrupt") - @mock.patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(0, lambda *args, **kwargs: 0)) - def test_afterrun_without_keyboard_interruption(self, call_mock, keyb_int, popen_mock): + def test_afterrun_without_keyboard_interruption(self, keyb_int, call_mock, popen_mock): config = {"poll.side_effect": lambda: 999} build_popen_mock(popen_mock, config) @@ -139,11 +133,6 @@ def test_run_hook_with_args(self): def test_run_hook_without_args(self): assert False, "Not yet implemented" - def test_afterrun_on_keyboard_interruption(self): - assert False, "Not yet implemented." - - def test_afterrun_with_exit_code(self): - assert False, "Not yet implemented." def test_onpass(self): assert False, "Not yet implemented." From bcfd391628b2bef11761b81b8bca2f62776bd4f2 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Mon, 15 Jan 2018 09:16:27 -0200 Subject: [PATCH 55/93] FileNotFoundError doesn't works for python2 ( Date: Sat, 20 Jan 2018 12:47:53 -0200 Subject: [PATCH 56/93] General synatx & typo fixes --- pytest_watch/tests/test_watcher.py | 8 ++++---- pytest_watch/tests/test_watcher_hooks.py | 4 ++-- pytest_watch/watcher.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py index 5271480..8accb68 100644 --- a/pytest_watch/tests/test_watcher.py +++ b/pytest_watch/tests/test_watcher.py @@ -1,3 +1,4 @@ +import errno import os import shutil import sys @@ -5,9 +6,9 @@ import unittest try: - from unittest.mock import patch + from unittest import mock except ImportError: - from mock import patch + import mock from pytest_watch.watcher import _split_recursive, run_hook, watch,\ _get_pytest_runner @@ -44,8 +45,7 @@ def test_invalid_directories(self): with pytest.raises(OSError) as err: watch(directories=[fake_dir]) - import errno - assert err.errno == errno.ENOENT + assert errno.ENOENT == err.value.errno def test_ignore_all_subdirs(self): dirs = [self.root_dir] diff --git a/pytest_watch/tests/test_watcher_hooks.py b/pytest_watch/tests/test_watcher_hooks.py index cb473d9..7ce945d 100644 --- a/pytest_watch/tests/test_watcher_hooks.py +++ b/pytest_watch/tests/test_watcher_hooks.py @@ -154,7 +154,7 @@ def test_onpass_on_not_tests_collected(self, call_mock, popen_mock): call_mock.assert_called_once_with(onpass, shell=True) @mock.patch("pytest_watch.watcher.beep") - def test_onfail_without_beep(self, beep_mock, call_mock, popen_mock): + def test_onfail_beep_off(self, beep_mock, call_mock, popen_mock): config = {"poll.side_effect": lambda: -1000} build_popen_mock(popen_mock, config) @@ -167,7 +167,7 @@ def test_onfail_without_beep(self, beep_mock, call_mock, popen_mock): beep_mock.assert_not_called() @mock.patch("pytest_watch.watcher.beep") - def test_onfail_without_beep(self, beep_mock, call_mock, popen_mock): + def test_onfail_beep_on(self, beep_mock, call_mock, popen_mock): config = {"poll.side_effect": lambda: -1000} build_popen_mock(popen_mock, config) diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index 50c684c..4ae32ca 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -218,7 +218,7 @@ def watch(directories=None, ignore=None, extensions=None, beep_on_failure=True, for directory in directories: if not os.path.isdir(directory): import errno - raise OSError('Directory not found: ' + directory, errno.ENOENT) + raise OSError(errno.ENOENT, 'Directory not found: ' + directory) # Setup event handler event_listener = EventListener(extensions) From 9a7ace09921b45e5385641254ba06068ea2ff2a5 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 21 Jan 2018 16:42:18 -0200 Subject: [PATCH 57/93] pep8 style over test_ code --- pytest_watch/command.py | 2 +- pytest_watch/config.py | 4 +-- pytest_watch/tests/test_command.py | 31 +++++++++++---------- pytest_watch/tests/test_watcher.py | 34 ++++++++++++----------- pytest_watch/tests/test_watcher_events.py | 13 ++++----- pytest_watch/tests/test_watcher_hooks.py | 31 ++++++++++----------- pytest_watch/watcher.py | 12 ++++++-- 7 files changed, 68 insertions(+), 59 deletions(-) diff --git a/pytest_watch/command.py b/pytest_watch/command.py index e0a6edd..f0475da 100644 --- a/pytest_watch/command.py +++ b/pytest_watch/command.py @@ -110,7 +110,7 @@ def main(argv=None): return 2 if spool < 0: - sys.stderr.write('Error: Spool value(--spool {}) must be positive'\ + sys.stderr.write('Error: Spool value(--spool {}) must be positive' ' integer\n' .format(spool)) return 2 diff --git a/pytest_watch/config.py b/pytest_watch/config.py index 3312636..87bc247 100644 --- a/pytest_watch/config.py +++ b/pytest_watch/config.py @@ -71,8 +71,8 @@ def _collect_config(pytest_args, silent=True): except (Exception, SystemExit): pass # Print message and run again without silencing - print('Error: Could not run --collect-only to handle the pytest config ' - 'file. Trying again without silencing output...', + print('Error: Could not run --collect-only to handle the pytest ' + 'config file. Trying again without silencing output...', file=sys.stderr) return _run_pytest_collect(pytest_args) diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index 3381502..03323f3 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -3,11 +3,6 @@ import shutil import tempfile -if sys.version_info[0] < 3: - from io import BytesIO as io_mock -else: - from io import StringIO as io_mock - try: from unittest.mock import patch except ImportError: @@ -17,6 +12,12 @@ from pytest_watch.constants import ALL_EXTENSIONS +if sys.version_info[0] < 3: + from io import BytesIO as io_mock +else: + from io import StringIO as io_mock + + @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) class TestCLIArguments(unittest.TestCase): @@ -230,8 +231,8 @@ def test_default_spool_value(self, watch_callee): def _assert_spool_error(self, watch_callee, value, err): with patch("pytest_watch.command.sys.stderr", new=io_mock()) as out: assert 2 == main(["--spool", value]) - assert err == out.getvalue(), \ - "Status code for invalid 'spool' argument should be 2" + assert err == out.getvalue(), ("Status code for invalid 'spool'" + " argument should be 2") watch_callee.assert_not_called() def test_cause_error_for_negative_spool_values(self, watch_callee): @@ -241,18 +242,18 @@ def test_cause_error_for_negative_spool_values(self, watch_callee): def test_cause_error_for_invalid_spool_values(self, watch_callee): value = "abc" self._assert_spool_error(watch_callee, value=value, - err="Error: Spool (--spool {}) must be" \ - " an integer.\n".format(value)) + err=str("Error: Spool (--spool {}) must be" + " an integer.\n").format(value)) value = "@" self._assert_spool_error(watch_callee, value=value, - err="Error: Spool (--spool {}) must be" \ - " an integer.\n".format(value)) + err=str("Error: Spool (--spool {}) must be" + " an integer.\n").format(value)) value = "[]" self._assert_spool_error(watch_callee, value=value, - err="Error: Spool (--spool {}) must be" \ - " an integer.\n".format(value)) + err=str("Error: Spool (--spool {}) must be" + " an integer.\n").format(value)) @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) @@ -270,9 +271,9 @@ def test_default_extensions(self, watch_callee): def test_all_extensions(self, watch_callee): main("--ext *".split()) - assert object == type(watch_callee.call_args[1]["extensions"]) + assert isinstance(watch_callee.call_args[1]["extensions"], object) - assert None != watch_callee.call_args[1]["extensions"] + assert None is not watch_callee.call_args[1]["extensions"] assert ALL_EXTENSIONS == watch_callee.call_args[1]["extensions"] diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py index 8accb68..5643d51 100644 --- a/pytest_watch/tests/test_watcher.py +++ b/pytest_watch/tests/test_watcher.py @@ -44,7 +44,7 @@ def test_invalid_directories(self): import pytest with pytest.raises(OSError) as err: - watch(directories=[fake_dir]) + watch(directories=[fake_dir]) assert errno.ENOENT == err.value.errno def test_ignore_all_subdirs(self): @@ -76,15 +76,17 @@ def test_ignore_subdirs_partially(self): ignore = [excluded_folder] - assert ([included_folder], [self.root_dir]) == \ - _split_recursive(dirs, ignore), \ - "Ignoring {1}, the following behavior is expected:\n"\ - ". {0} should be loaded non-recursivelly;\n"\ - ". {1} and its children will be excluded;\n"\ - ". only {2} will be loaded recursivelly.\n"\ - .format(self.root_dir, excluded_folder, included_folder) + fail_err = str("Ignoring {1}, the following behavior is expected:\n" + ". {0} should be loaded non-recursivelly;\n" + ". {1} and its children will be excluded;\n" + ". only {2} will be loaded recursivelly.\n") + fail_msg = fail_err.format(self.root_dir, excluded_folder, + included_folder) - @unittest.skip("Depends on pytest_watch.watcher._split_recursive support"\ + result = _split_recursive(dirs, ignore) + assert ([included_folder], [self.root_dir]) == result, fail_msg + + @unittest.skip("Depends on pytest_watch.watcher._split_recursive support" " for deep recursive navigation through directory tree") def test_ignore_deep_subtree_multichild(self): """ @@ -110,10 +112,10 @@ def test_ignore_deep_subtree_multichild(self): ignore = [subtree_folder] - assert ([self.root_dir], [tree_folder]) == \ - _split_recursive(dirs, ignore) + assert ([self.root_dir], [tree_folder]) == _split_recursive(dirs, + ignore) - @unittest.skip("Depends on pytest_watch.watcher._split_recursive support"\ + @unittest.skip("Depends on pytest_watch.watcher._split_recursive support" " for deep recursive navigation through directory tree") def test_ignore_deep_subtree_single(self): """ @@ -136,8 +138,8 @@ def test_ignore_deep_subtree_single(self): ignore = [subtree_folder] - assert ([self.root_dir], [tree_folder]) == \ - _split_recursive(dirs, ignore) + assert ([self.root_dir], [tree_folder]) == _split_recursive(dirs, + ignore) class TestPytestRunner(unittest.TestCase): @@ -158,11 +160,11 @@ def test_default_sys_executable(self): def test_empty_string_returns_sys_executable(self): assert TestPytestRunner.DEFAULT_EXECUTABLE == _get_pytest_runner("") assert TestPytestRunner.DEFAULT_EXECUTABLE == _get_pytest_runner(" ") - assert TestPytestRunner.DEFAULT_EXECUTABLE == _get_pytest_runner(" "*80) + assert TestPytestRunner.DEFAULT_EXECUTABLE == _get_pytest_runner(" "*8) def test_custom_sys_executable(self): assert ["mypytest"] == _get_pytest_runner("mypytest") - assert ["mypytest", "runtest"] == _get_pytest_runner("mypytest runtest") + assert ["mpytest", "runtest"] == _get_pytest_runner("mpytest runtest") def test_virtualenv_executable(self): os.environ["VIRTUAL_ENV"] = "/tmp/venv" diff --git a/pytest_watch/tests/test_watcher_events.py b/pytest_watch/tests/test_watcher_events.py index 219e3e3..61101c5 100644 --- a/pytest_watch/tests/test_watcher_events.py +++ b/pytest_watch/tests/test_watcher_events.py @@ -7,11 +7,13 @@ except: import mock -from watchdog.events import FileModifiedEvent, FileMovedEvent, FileCreatedEvent, \ - FileDeletedEvent, FileCreatedEvent, DirModifiedEvent, FileSystemEvent +from watchdog.events import FileModifiedEvent, FileMovedEvent, \ + FileCreatedEvent, FileDeletedEvent, FileCreatedEvent, DirModifiedEvent, \ + FileSystemEvent from pytest_watch.constants import ALL_EXTENSIONS from pytest_watch.watcher import EventListener +from pytest_watch.watcher import os as wos def _assert_watched_filesystem_event(event, event_listener=None): @@ -49,9 +51,6 @@ def test_file_delete_event(): _assert_watched_filesystem_event(FileDeletedEvent("/tmp/file.py")) -from pytest_watch.watcher import os as wos - - @mock.patch.object(wos.path, "relpath") def test_file_move_event(relpath): relpath.side_effect = lambda *args, **kwargs: args[0] @@ -60,8 +59,8 @@ def test_file_move_event(relpath): _assert_watched_filesystem_event(FileMovedEvent(src_path, dest_path)) - assert 2 == relpath.call_count, \ - "os.path.relpath should be called twice when file is moved src,dst" + assert 2 == relpath.call_count, str("os.path.relpath should be called " + "twice when file is moved src,dst") relpath.assert_any_call(src_path) relpath.assert_any_call(dest_path) diff --git a/pytest_watch/tests/test_watcher_hooks.py b/pytest_watch/tests/test_watcher_hooks.py index 7ce945d..fa1f609 100644 --- a/pytest_watch/tests/test_watcher_hooks.py +++ b/pytest_watch/tests/test_watcher_hooks.py @@ -12,6 +12,7 @@ from pytest_watch.constants import EXIT_NOTESTSCOLLECTED, EXIT_OK from pytest_watch.watcher import watch, run_hook from pytest_watch.watcher import subprocess as wsubprocess +from pytest_watch import watcher def build_popen_mock(popen, config): @@ -43,7 +44,7 @@ def _wrapped(*args, **kwargs): class TestRunHooksBasic(unittest.TestCase): @mock.patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(0, _subcall)) + side_effect=assertion_wrapper(0, _subcall)) def test_run_hook_systemexit_0(self, call_mock): python_exec = sys.executable cmd_parts = [python_exec, "-c", "'exit(0)'"] @@ -52,7 +53,7 @@ def test_run_hook_systemexit_0(self, call_mock): call_mock.assert_called_once_with(cmd, shell=True) @mock.patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(1, _subcall)) + side_effect=assertion_wrapper(1, _subcall)) def test_run_hook_systemexit_not_0(self, call_mock): python_exec = sys.executable cmd_parts = [python_exec, "-c", "'exit(1)'"] @@ -61,9 +62,9 @@ def test_run_hook_systemexit_not_0(self, call_mock): call_mock.assert_called_once_with(cmd, shell=True) -from pytest_watch import watcher orig = watcher.run_hook + def get_hook_stop_iteration(command_str): def hook(cmd, *args): orig(cmd, *args) @@ -85,7 +86,7 @@ def test_with_beforerun(self, call_mock, popen_mock): "wait.return_value": 0} build_popen_mock(popen_mock, config) - beforerun="{} -c 'exit(0) #it is beforerun'".format(sys.executable) + beforerun = "{} -c 'exit(0) #it is beforerun'".format(sys.executable) watch(beforerun=beforerun) @@ -98,7 +99,7 @@ def test_afterrun_on_keyboard_interruption(self, keyb_int, call_mock, "wait.return_value": 10} build_popen_mock(popen_mock, config) - afterrun="{} -m this".format(sys.executable) + afterrun = "{} -m this".format(sys.executable) watch(afterrun=afterrun, wait=True) @@ -106,7 +107,7 @@ def test_afterrun_on_keyboard_interruption(self, keyb_int, call_mock, call_mock.assert_called_once() - expected_cmd = afterrun + " 10" # should run with p.wait() arg + expected_cmd = afterrun + " 10" # should run with p.wait() arg call_mock.assert_called_once_with(expected_cmd, shell=True) @@ -116,7 +117,7 @@ def test_afterrun_without_keyboard_interruption(self, keyb_int, call_mock, config = {"poll.side_effect": lambda: 999} build_popen_mock(popen_mock, config) - afterrun="{} -c 'exit(0) #it is afterrun'".format(sys.executable) + afterrun = "{} -c 'exit(0) #it is afterrun'".format(sys.executable) watcher.run_hook = get_hook_stop_iteration(afterrun) @@ -126,7 +127,7 @@ def test_afterrun_without_keyboard_interruption(self, keyb_int, call_mock, call_mock.assert_called_once() - expected_cmd = afterrun + " 999" # should run with exit_code arg + expected_cmd = afterrun + " 999" # should run with exit_code arg call_mock.assert_called_once_with(expected_cmd, shell=True) @@ -134,8 +135,8 @@ def test_onpass_on_exit(self, call_mock, popen_mock): config = {"poll.side_effect": lambda: EXIT_OK} build_popen_mock(popen_mock, config) - onpass="{} -c 'exit(0) #it is afterpass on exit'"\ - .format(sys.executable) + onpass = "{} -c 'exit(0) #it is afterpass on exit'" \ + .format(sys.executable) watcher.run_hook = get_hook_stop_iteration(onpass) watch(onpass=onpass, wait=True) @@ -146,8 +147,8 @@ def test_onpass_on_not_tests_collected(self, call_mock, popen_mock): config = {"poll.side_effect": lambda: EXIT_NOTESTSCOLLECTED} build_popen_mock(popen_mock, config) - onpass="{} -c 'exit(0) #it is afterpass on not_tests_collected'"\ - .format(sys.executable) + onpass = "{} -c 'exit(0) #it is afterpass on not_tests_collected'" \ + .format(sys.executable) watcher.run_hook = get_hook_stop_iteration(onpass) watch(onpass=onpass, wait=True) @@ -158,8 +159,7 @@ def test_onfail_beep_off(self, beep_mock, call_mock, popen_mock): config = {"poll.side_effect": lambda: -1000} build_popen_mock(popen_mock, config) - onfail="{} -c 'exit(1) # failure happens'"\ - .format(sys.executable) + onfail = "{} -c 'exit(1) # failure happens'".format(sys.executable) watcher.run_hook = get_hook_stop_iteration(onfail) watch(onfail=onfail, wait=True, beep_on_failure=False) @@ -171,8 +171,7 @@ def test_onfail_beep_on(self, beep_mock, call_mock, popen_mock): config = {"poll.side_effect": lambda: -1000} build_popen_mock(popen_mock, config) - onfail="{} -c 'exit(1) # failure happens'"\ - .format(sys.executable) + onfail = "{} -c 'exit(1) # failure happens'".format(sys.executable) watcher.run_hook = get_hook_stop_iteration(onfail) watch(onfail=onfail, wait=True, beep_on_failure=True) diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index 4ae32ca..b497417 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -119,10 +119,18 @@ def _reduce_events(events): return filtered +def _bright(arg): + return STYLE_BRIGHT + arg + Style.RESET_ALL + + +def _highlight(arg): + return STYLE_HIGHLIGHT + arg + Style.RESET_ALL + + def _show_summary(argv, events, verbose=False): command = ' '.join(argv) - bright = lambda arg: STYLE_BRIGHT + arg + Style.RESET_ALL - highlight = lambda arg: STYLE_HIGHLIGHT + arg + Style.RESET_ALL + bright = _bright + highlight = _highlight time_stamp = time.strftime("%c", time.localtime(time.time())) run_command_info = '[{}] Running: {}'.format(time_stamp, From 13029b70c4bf0a09ac59c0488c81ceb7ace91dd3 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Mon, 22 Jan 2018 10:44:36 -0200 Subject: [PATCH 58/93] Added https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore .gitignore --- .gitignore | 118 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 102 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index b364795..71627a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,106 @@ -# Deployment files -*.egg-info -dist - -# Environment files -site-packages -build -env -.cache -*.so +# source: https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore +# +# Byte-compiled / optimized / DLL files +__pycache__/ *.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec -# OS-specific files -.DS_Store -Desktop.ini -Thumbs.db +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ .coverage .coverage.* -venv -*.prof +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ From 515d8981e0296c2dc9c202aba94aad48f9c6f567 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Mon, 22 Jan 2018 10:46:13 -0200 Subject: [PATCH 59/93] Change .gitignore reference --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 71627a0..6bfd622 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# source: https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore +# source: https://github.com/github/gitignore/blob/master/Python.gitignore # # Byte-compiled / optimized / DLL files __pycache__/ From da3e334b75022284f2ea5a5c629a46db0b7f4540 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 11 Feb 2018 10:05:50 -0200 Subject: [PATCH 60/93] pep8 imports --- pytest_watch/tests/test_watcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py index 5643d51..7745630 100644 --- a/pytest_watch/tests/test_watcher.py +++ b/pytest_watch/tests/test_watcher.py @@ -10,6 +10,8 @@ except ImportError: import mock +import pytest + from pytest_watch.watcher import _split_recursive, run_hook, watch,\ _get_pytest_runner @@ -41,8 +43,6 @@ def test_invalid_directories(self): fake_dir = os.path.join(self.root_dir, "atrocadocapacausti") - import pytest - with pytest.raises(OSError) as err: watch(directories=[fake_dir]) assert errno.ENOENT == err.value.errno From a0fbe45158fc14f964eead9e67a7fe2b7928a3ab Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 11 Feb 2018 10:21:41 -0200 Subject: [PATCH 61/93] Added supported python versions to travis --- .travis.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1a25dbf..3f7b1ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,14 @@ language: python python: - - "3.6.1" - - "2.7.14" + - 2.7 + - pypy2.7-5.8.0 + - 3.4 + - 3.5 + - 3.6 + - 3.7-dev + - nightly + - pypy3.5-5.8.0 + os: - linux From 06b6da6d7760b95aac5e530c2d2af7849ffc8614 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 11 Feb 2018 11:38:14 -0200 Subject: [PATCH 62/93] Change mock assertion calls: assert_called_once for compatibility with python 3.5 version. There are some discussions around about convenience usage of assert_called_once, as this overview writen by Yelp team: https://engineeringblog.yelp.com/2015/02/assert_called_once-threat-or-menace.html --- .travis.yml | 2 -- pytest_watch/tests/test_command.py | 44 ++++++++++++------------ pytest_watch/tests/test_watcher_hooks.py | 6 ++-- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3f7b1ee..db10fa3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ python: - 3.4 - 3.5 - 3.6 - - 3.7-dev - - nightly - pypy3.5-5.8.0 os: diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index 03323f3..f83657e 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -45,7 +45,7 @@ def _get_default_args(self): def test_default_parameters(self, watch_callee): main([]) - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count watch_callee.assert_called_once_with(**self._get_default_args()) def test_empty_argv(self, watch_callee): @@ -53,7 +53,7 @@ def test_empty_argv(self, watch_callee): main() - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count watch_callee.assert_called_once_with(**self._get_default_args()) @@ -204,14 +204,14 @@ def test_zero_spool_value(self, watch_callee): assert "spool" in watch_callee.call_args[1] assert 0 == watch_callee.call_args[1]["spool"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_positive_spool_value(self, watch_callee): main("--spool 2000".split()) assert "spool" in watch_callee.call_args[1] assert 2000 == watch_callee.call_args[1]["spool"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count watch_callee.reset_mock() @@ -219,14 +219,14 @@ def test_positive_spool_value(self, watch_callee): assert "spool" in watch_callee.call_args[1] assert 20 == watch_callee.call_args[1]["spool"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_default_spool_value(self, watch_callee): main([]) assert "spool" in watch_callee.call_args[1] assert 200 == watch_callee.call_args[1]["spool"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def _assert_spool_error(self, watch_callee, value, err): with patch("pytest_watch.command.sys.stderr", new=io_mock()) as out: @@ -266,7 +266,7 @@ def test_default_extensions(self, watch_callee): assert [".py"] == watch_callee.call_args[1]["extensions"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_all_extensions(self, watch_callee): main("--ext *".split()) @@ -277,7 +277,7 @@ def test_all_extensions(self, watch_callee): assert ALL_EXTENSIONS == watch_callee.call_args[1]["extensions"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_single_without_dot_extensions(self, watch_callee): main("--ext py".split()) @@ -286,7 +286,7 @@ def test_single_without_dot_extensions(self, watch_callee): assert [".py"] == watch_callee.call_args[1]["extensions"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_single_with_dot_extensions(self, watch_callee): main("--ext .py".split()) @@ -295,7 +295,7 @@ def test_single_with_dot_extensions(self, watch_callee): assert [".py"] == watch_callee.call_args[1]["extensions"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_multiple_extensions(self, watch_callee): main("--ext .py,.html".split()) @@ -304,7 +304,7 @@ def test_multiple_extensions(self, watch_callee): assert [".py", ".html"] == watch_callee.call_args[1]["extensions"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_multiple_with_and_without_dots_extensions(self, watch_callee): main("--ext .py,html".split()) @@ -313,7 +313,7 @@ def test_multiple_with_and_without_dots_extensions(self, watch_callee): assert [".py", ".html"] == watch_callee.call_args[1]["extensions"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count watch_callee.reset_mock() @@ -323,7 +323,7 @@ def test_multiple_with_and_without_dots_extensions(self, watch_callee): assert [".py", ".html"] == watch_callee.call_args[1]["extensions"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) @@ -340,7 +340,7 @@ def test_no_directory_empty_pytest_arg(self, watch_callee): assert "pytest_args" in watch_callee.call_args[1] assert [] == watch_callee.call_args[1]["pytest_args"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_no_directory_single_pytest_arg(self, watch_callee): main("-- --pdb".split()) @@ -349,7 +349,7 @@ def test_no_directory_single_pytest_arg(self, watch_callee): assert ["--pdb"] == watch_callee.call_args[1]["pytest_args"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_no_directory_multiple_pytest_args(self, watch_callee): main("-- --pdb --cov=.".split()) @@ -358,7 +358,7 @@ def test_no_directory_multiple_pytest_args(self, watch_callee): assert ["--pdb", "--cov=."] == watch_callee.call_args[1]["pytest_args"] - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_multiple_directory_no_pytest_args(self, watch_callee): directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(2)] @@ -377,7 +377,7 @@ def test_multiple_directory_no_pytest_args(self, watch_callee): assert len(fetched_pytest_args) > 1 assert len(fetched_pytest_args) == len(fetched_directories) assert fetched_directories == fetched_pytest_args - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_single_directory_no_pytest_args(self, watch_callee): main([self.root_tmp, "--"]) @@ -388,7 +388,7 @@ def test_single_directory_no_pytest_args(self, watch_callee): assert len(pytest_args) > 0 assert [self.root_tmp] == pytest_args - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count fetched_directories = watch_callee.call_args[1]["directories"] assert [self.root_tmp] == fetched_directories @@ -409,7 +409,7 @@ def test_single_directory_single_pytest_args(self, watch_callee): pytest_args = watch_callee.call_args[1]["pytest_args"] assert len(pytest_args) > 0 assert [self.root_tmp, "--pdb"] == pytest_args - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count assert [self.root_tmp] == fetched_directories @@ -431,7 +431,7 @@ def test_single_directory_multiple_pytest_args(self, watch_callee): assert [self.root_tmp, "--pdb", "--cov=."] == pytest_args - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count assert [self.root_tmp] == fetched_directories @@ -455,7 +455,7 @@ def test_default_directories(self, watch_callee): fetched_directories = watch_callee.call_args[1]["directories"] assert directories == fetched_directories - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count def test_single_directory(self): directories = [self.root_tmp] @@ -482,4 +482,4 @@ def _assert_directories(self, directories, watch_callee=None): assert len(directories) == len(fetched_directories) assert directories == fetched_directories - watch_callee.assert_called_once() + assert 1 == watch_callee.call_count diff --git a/pytest_watch/tests/test_watcher_hooks.py b/pytest_watch/tests/test_watcher_hooks.py index fa1f609..14e7612 100644 --- a/pytest_watch/tests/test_watcher_hooks.py +++ b/pytest_watch/tests/test_watcher_hooks.py @@ -105,7 +105,7 @@ def test_afterrun_on_keyboard_interruption(self, keyb_int, call_mock, keyb_int.assert_not_called() - call_mock.assert_called_once() + assert 1 == call_mock.call_count expected_cmd = afterrun + " 10" # should run with p.wait() arg @@ -125,7 +125,7 @@ def test_afterrun_without_keyboard_interruption(self, keyb_int, call_mock, keyb_int.assert_not_called() - call_mock.assert_called_once() + assert 1 == call_mock.call_count expected_cmd = afterrun + " 999" # should run with exit_code arg @@ -176,7 +176,7 @@ def test_onfail_beep_on(self, beep_mock, call_mock, popen_mock): watch(onfail=onfail, wait=True, beep_on_failure=True) call_mock.assert_called_once_with(onfail, shell=True) - beep_mock.assert_called_once() + assert 1 == beep_mock.call_count @unittest.skip("baby steps") From 5c4ff376abde187045f99395b88d6cb674ed2086 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 11 Feb 2018 17:41:24 -0200 Subject: [PATCH 63/93] travis test over 3.5 and 3.7-dev --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index db10fa3..bef2ec9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,10 @@ python: - 2.7 - pypy2.7-5.8.0 - 3.4 - - 3.5 + - 3.5.4 - 3.6 - pypy3.5-5.8.0 + - 3.7-dev os: - linux From 7adcb13bec877dad5a8b794573f8e417cba13bd8 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 11 Feb 2018 17:44:43 -0200 Subject: [PATCH 64/93] travis remove tests over 3.7-dev --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bef2ec9..5254c38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ python: - 3.5.4 - 3.6 - pypy3.5-5.8.0 - - 3.7-dev os: - linux From 0be301a7306d72e102ba4cefb8dcdd9e96a75984 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 22 Feb 2018 20:19:42 -0300 Subject: [PATCH 65/93] Reverting .gitignore to lean version containing only project inherent ignored resources --- .gitignore | 117 ++++++++--------------------------------------------- 1 file changed, 16 insertions(+), 101 deletions(-) diff --git a/.gitignore b/.gitignore index 6bfd622..7e1a2c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,106 +1,21 @@ -# source: https://github.com/github/gitignore/blob/master/Python.gitignore -# -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions +# Deployment files +*.egg-info +dist + +# Environment files +site-packages +build +env +.cache *.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ +*.py[cod] +.eggs .coverage .coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -.static_storage/ -.media/ -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv +.pytest_cache .python-version -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ +# OS-specific files +.DS_Store +Desktop.ini +Thumbs.db From 6f1638ccf725953965b5521044c5beb3b5fbbec0 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 22 Feb 2018 20:21:08 -0300 Subject: [PATCH 66/93] Initial clean up for unittest.TestCase and other dependencies of framework --- pytest_watch/tests/test_command.py | 30 +++++++++++------------ pytest_watch/tests/test_main.py | 1 - pytest_watch/tests/test_watcher.py | 18 +++++++------- pytest_watch/tests/test_watcher_events.py | 7 +++--- pytest_watch/tests/test_watcher_hooks.py | 10 ++++---- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index f83657e..d44d95b 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -1,5 +1,4 @@ import sys -import unittest import shutil import tempfile @@ -19,7 +18,7 @@ @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) -class TestCLIArguments(unittest.TestCase): +class TestCLIArguments(): def _get_default_args(self): return dict( @@ -58,7 +57,7 @@ def test_empty_argv(self, watch_callee): @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) -class TestPdbArgument(unittest.TestCase): +class TestPdbArgument(): def test_default_pdb_argument(self, watch_callee): sys.argv[1:] = [] @@ -102,7 +101,8 @@ def test_pdb_off_and_wait_on_arguments(self, watch_callee): @patch("pytest_watch.command.merge_config", side_effect=lambda *args, **kwargs: True) @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) -class TestConfigArgument(unittest.TestCase): +class TestConfigArgument(): + def test_default_config(self, watch_callee, merge_config_callee): sys.argv[1:] = [] @@ -127,12 +127,12 @@ def _assert_config_file(self, watch_callee, filename): @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) -class TestIgnoreArgument(unittest.TestCase): +class TestIgnoreArgument(): - def setUp(self): + def setup_method(self): self.root_tmp = tempfile.mkdtemp() - def tearDown(self): + def teardown_method(self): shutil.rmtree(self.root_tmp) def test_default_ignore_argument(self, watch_callee): @@ -196,7 +196,7 @@ def test_multiple_ignore_argument_conflict(self, watch_callee): @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) -class TestSpoolArguments(unittest.TestCase): +class TestSpoolArguments(): def test_zero_spool_value(self, watch_callee): main("--spool 0".split()) @@ -257,7 +257,7 @@ def test_cause_error_for_invalid_spool_values(self, watch_callee): @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) -class TestExtensionsArguments(unittest.TestCase): +class TestExtensionsArguments(): def test_default_extensions(self, watch_callee): main([]) @@ -327,12 +327,12 @@ def test_multiple_with_and_without_dots_extensions(self, watch_callee): @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) -class TestDirectoriesAndPytestArgsArgumentsSplit(unittest.TestCase): +class TestDirectoriesAndPytestArgsArgumentsSplit(): - def setUp(self): + def setup_method(self): self.root_tmp = tempfile.mkdtemp() - def tearDown(self): + def teardown_method(self): shutil.rmtree(self.root_tmp) def test_no_directory_empty_pytest_arg(self, watch_callee): @@ -436,12 +436,12 @@ def test_single_directory_multiple_pytest_args(self, watch_callee): assert [self.root_tmp] == fetched_directories -class TestDirectoriesArguments(unittest.TestCase): +class TestDirectoriesArguments(): - def setUp(self): + def setup_method(self): self.root_tmp = tempfile.mkdtemp() - def tearDown(self): + def teardown_method(self): shutil.rmtree(self.root_tmp) @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) diff --git a/pytest_watch/tests/test_main.py b/pytest_watch/tests/test_main.py index 855809f..5d616f3 100644 --- a/pytest_watch/tests/test_main.py +++ b/pytest_watch/tests/test_main.py @@ -1,6 +1,5 @@ import os import sys -import unittest try: from unittest.mock import patch diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py index 7745630..51e8e2b 100644 --- a/pytest_watch/tests/test_watcher.py +++ b/pytest_watch/tests/test_watcher.py @@ -3,7 +3,7 @@ import shutil import sys import tempfile -import unittest +from unittest import skip try: from unittest import mock @@ -16,12 +16,12 @@ _get_pytest_runner -class TestDirectoriesFiltering(unittest.TestCase): +class TestDirectoriesFiltering(): - def setUp(self): + def setup_method(self): self.root_dir = tempfile.mkdtemp() - def tearDown(self): + def teardown_method(self): try: shutil.rmtree(self.root_dir) except: @@ -86,7 +86,7 @@ def test_ignore_subdirs_partially(self): result = _split_recursive(dirs, ignore) assert ([included_folder], [self.root_dir]) == result, fail_msg - @unittest.skip("Depends on pytest_watch.watcher._split_recursive support" + @skip("Depends on pytest_watch.watcher._split_recursive support" " for deep recursive navigation through directory tree") def test_ignore_deep_subtree_multichild(self): """ @@ -115,7 +115,7 @@ def test_ignore_deep_subtree_multichild(self): assert ([self.root_dir], [tree_folder]) == _split_recursive(dirs, ignore) - @unittest.skip("Depends on pytest_watch.watcher._split_recursive support" + @skip("Depends on pytest_watch.watcher._split_recursive support" " for deep recursive navigation through directory tree") def test_ignore_deep_subtree_single(self): """ @@ -142,15 +142,15 @@ def test_ignore_deep_subtree_single(self): ignore) -class TestPytestRunner(unittest.TestCase): +class TestPytestRunner(): DEFAULT_EXECUTABLE = [sys.executable, "-m", "pytest"] - def setUp(self): + def setup_method(self): self.virtual_env = os.getenv("VIRTUAL_ENV") if "VIRTUAL_ENV" in os.environ: del os.environ['VIRTUAL_ENV'] - def tearDown(self): + def teardown_method(self): if self.virtual_env: os.putenv("VIRTUAL_ENV", self.virtual_env) diff --git a/pytest_watch/tests/test_watcher_events.py b/pytest_watch/tests/test_watcher_events.py index 61101c5..c3a1471 100644 --- a/pytest_watch/tests/test_watcher_events.py +++ b/pytest_watch/tests/test_watcher_events.py @@ -1,6 +1,5 @@ import shutil import tempfile -import unittest try: from unittest import mock @@ -66,11 +65,11 @@ def test_file_move_event(relpath): relpath.assert_any_call(dest_path) -class TestExtensionsMatch(unittest.TestCase): - def setUp(self): +class TestExtensionsMatch(): + def setup_method(self): self.root_dir = tempfile.mkdtemp() - def tearDown(self): + def teardown_method(self): try: shutil.rmtree(self.root_dir) except: diff --git a/pytest_watch/tests/test_watcher_hooks.py b/pytest_watch/tests/test_watcher_hooks.py index 14e7612..a1d465b 100644 --- a/pytest_watch/tests/test_watcher_hooks.py +++ b/pytest_watch/tests/test_watcher_hooks.py @@ -1,13 +1,13 @@ import subprocess from subprocess import call as _subcall import sys -import unittest try: from unittest import mock except ImportError: import mock +import pytest from pytest_watch.constants import EXIT_NOTESTSCOLLECTED, EXIT_OK from pytest_watch.watcher import watch, run_hook @@ -41,7 +41,7 @@ def _wrapped(*args, **kwargs): return _wrapped -class TestRunHooksBasic(unittest.TestCase): +class TestRunHooksBasic(): @mock.patch("pytest_watch.watcher.subprocess.call", side_effect=assertion_wrapper(0, _subcall)) @@ -76,7 +76,7 @@ def hook(cmd, *args): @mock.patch.object(wsubprocess, "Popen") @mock.patch("pytest_watch.watcher.subprocess.call", side_effect=lambda *args, **kwargs: 0) -class TestRunHookCallbacks(unittest.TestCase): +class TestRunHookCallbacks(): def test_with_beforerun(self, call_mock, popen_mock): """ @@ -179,8 +179,8 @@ def test_onfail_beep_on(self, beep_mock, call_mock, popen_mock): assert 1 == beep_mock.call_count -@unittest.skip("baby steps") -class TestRunHooksSkiped(unittest.TestCase): +@pytest.mark.skip("baby steps") +class TestRunHooksSkiped(): def test_run_hook_with_args(self): assert False, "Not yet implemented" From 81f5c63f31a39a00d1f7e2759dd1bb40efb8ffe8 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 22 Feb 2018 21:42:42 -0300 Subject: [PATCH 67/93] requirements.txt and requirements-test.txt dependencies were inserted into setup.py extras_require was added to config mock module conditional installation was moved to extras_require section, simplifying duplicated verification --- .travis.yml | 4 +--- setup.py | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5254c38..3179fd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,9 +13,7 @@ os: cache: pip install: - - pip install -r requirements.txt - - pip install -r requirements-test.txt - - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install --allow-all-external mock; fi + - pip install -e ".[qa]" script: - python -m pytest --cov=pytest_watch pytest_watch diff --git a/setup.py b/setup.py index 8dde742..b073c5e 100644 --- a/setup.py +++ b/setup.py @@ -8,13 +8,10 @@ def read(filename): return f.read() -def get_test_requirements(): - reqs = read('requirements-test.txt').splitlines() - - if sys.version_info[0] < 3: - reqs.append("mock") - - return reqs +DEPS_MAIN = ["colorama>=0.3.3", "docopt>=0.6.2", "pytest>=2.6.4", + "watchdog>=0.6.0"] +DEPS_QA = ["pytest-cov>=2.5.1"] +DEPS_TESTING = [] setup( @@ -28,12 +25,20 @@ def get_test_requirements(): license='MIT', platforms='any', packages=find_packages(), - install_requires=read('requirements.txt').splitlines(), - tests_require=get_test_requirements(), + install_requires=DEPS_MAIN, + tests_require=DEPS_TESTING, entry_points={ 'console_scripts': [ 'pytest-watch = pytest_watch:main', 'ptw = pytest_watch:main', ] }, + extras_require={ + 'testing': DEPS_TESTING, + 'dev': DEPS_TESTING + DEPS_QA + ['pdbpp', 'pytest-pdb'], + 'qa': DEPS_QA, + 'testing:python_version in "2.6, 2.7, 3.2"': ['mock'], + 'dev:python_version in "2.6, 2.7, 3.2"': ['mock'], + 'qa:python_version in "2.6, 2.7, 3.2"': ['mock'], + }, ) From 38496361e49c680e9fae5a23bf5878e498810ca9 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Fri, 23 Feb 2018 15:28:28 -0300 Subject: [PATCH 68/93] Move default pytest_watcher.watch mock to pytest-mock mocker usage pattern. Initial usage of conftest.py and fixtures --- pytest_watch/tests/conftest.py | 15 ++++ pytest_watch/tests/test_command.py | 110 ++++++++++++----------------- 2 files changed, 62 insertions(+), 63 deletions(-) create mode 100644 pytest_watch/tests/conftest.py diff --git a/pytest_watch/tests/conftest.py b/pytest_watch/tests/conftest.py new file mode 100644 index 0000000..ade5818 --- /dev/null +++ b/pytest_watch/tests/conftest.py @@ -0,0 +1,15 @@ +import pytest + + +@pytest.fixture +def merge_config_callee(mocker): + m = mocker.patch("pytest_watch.command.merge_config", + side_effect=lambda *args, **kwargs: True) + return m + + +@pytest.fixture +def watch_callee(mocker): + watch_mock = mocker.patch("pytest_watch.command.watch") + watch_mock.return_value.side_effect=lambda *args, **kwargs: 0 + return watch_mock diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index d44d95b..b39e41b 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -1,6 +1,6 @@ +import pytest import sys import shutil -import tempfile try: from unittest.mock import patch @@ -17,7 +17,7 @@ from io import StringIO as io_mock -@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +@pytest.mark.usefixtures("watch_callee") class TestCLIArguments(): def _get_default_args(self): @@ -56,7 +56,7 @@ def test_empty_argv(self, watch_callee): watch_callee.assert_called_once_with(**self._get_default_args()) -@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +@pytest.mark.usefixtures("watch_callee") class TestPdbArgument(): def test_default_pdb_argument(self, watch_callee): @@ -98,9 +98,7 @@ def test_pdb_off_and_wait_on_arguments(self, watch_callee): assert "--pdb" not in watch_callee.call_args[1]["pytest_args"] -@patch("pytest_watch.command.merge_config", - side_effect=lambda *args, **kwargs: True) -@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +@pytest.mark.usefixtures("watch_callee", "merge_config_callee") class TestConfigArgument(): def test_default_config(self, watch_callee, merge_config_callee): @@ -126,15 +124,9 @@ def _assert_config_file(self, watch_callee, filename): assert filename == pytest_args[pytest_args.index("-c")+1] -@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) -class TestIgnoreArgument(): - - def setup_method(self): - self.root_tmp = tempfile.mkdtemp() - - def teardown_method(self): - shutil.rmtree(self.root_tmp) +@pytest.mark.usefixtures("watch_callee", "tmpdir_factory") +class TestIgnoreArgument(): def test_default_ignore_argument(self, watch_callee): sys.argv[1:] = [] @@ -144,6 +136,7 @@ def test_default_ignore_argument(self, watch_callee): assert "--ignore" not in watch_callee.call_args[1]["pytest_args"] + def test_ignore_argument(self, watch_callee): main(["--ignore", "pytest_watch"]) @@ -151,12 +144,13 @@ def test_ignore_argument(self, watch_callee): assert "--ignore" in watch_callee.call_args[1]["pytest_args"] - def test_multiple_ignore_argument(self, watch_callee): + + def test_multiple_ignore_argument(self, tmpdir_factory, watch_callee): directories = [] argv = [] for _ in range(2): - new_dir = tempfile.mkdtemp(dir=self.root_tmp) + new_dir = str(tmpdir_factory.mktemp("_")) argv.append("--ignore") argv.append(new_dir) directories.append(new_dir) @@ -164,27 +158,26 @@ def test_multiple_ignore_argument(self, watch_callee): main(argv) assert directories == watch_callee.call_args[1]["ignore"] - pytest_args = watch_callee.call_args[1]["pytest_args"] - assert "--ignore" in pytest_args ignore_idx = pytest_args.index("--ignore") assert argv == pytest_args - def test_multiple_ignore_argument_conflict(self, watch_callee): + + def test_multiple_ignore_argument_conflict(self, tmpdir_factory, watch_callee): directories = [] argv = [] for _ in range(2): - new_dir = tempfile.mkdtemp(dir=self.root_tmp) + new_dir = str(tmpdir_factory.mktemp("_")) argv.append("--ignore") argv.append(new_dir) directories.append(new_dir) argv.append("--") argv.append("--ignore") - argv.append(tempfile.mkdtemp(dir=self.root_tmp)) + argv.append(str(tmpdir_factory.mktemp("_"))) main(argv) @@ -195,7 +188,7 @@ def test_multiple_ignore_argument_conflict(self, watch_callee): assert 3 == pytest_args.count("--ignore") -@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +@pytest.mark.usefixtures("watch_callee") class TestSpoolArguments(): def test_zero_spool_value(self, watch_callee): @@ -256,7 +249,7 @@ def test_cause_error_for_invalid_spool_values(self, watch_callee): " an integer.\n").format(value)) -@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +@pytest.mark.usefixtures("watch_callee") class TestExtensionsArguments(): def test_default_extensions(self, watch_callee): @@ -326,15 +319,9 @@ def test_multiple_with_and_without_dots_extensions(self, watch_callee): assert 1 == watch_callee.call_count -@patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) +@pytest.mark.usefixtures("watch_callee", "tmpdir") class TestDirectoriesAndPytestArgsArgumentsSplit(): - def setup_method(self): - self.root_tmp = tempfile.mkdtemp() - - def teardown_method(self): - shutil.rmtree(self.root_tmp) - def test_no_directory_empty_pytest_arg(self, watch_callee): main(["--"]) @@ -360,8 +347,8 @@ def test_no_directory_multiple_pytest_args(self, watch_callee): assert 1 == watch_callee.call_count - def test_multiple_directory_no_pytest_args(self, watch_callee): - directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(2)] + def test_multiple_directory_no_pytest_args(self, tmpdir_factory, watch_callee): + directories = [str(tmpdir_factory.mktemp("_")) for _ in range(2)] directories.append("--") main(directories) @@ -379,22 +366,25 @@ def test_multiple_directory_no_pytest_args(self, watch_callee): assert fetched_directories == fetched_pytest_args assert 1 == watch_callee.call_count - def test_single_directory_no_pytest_args(self, watch_callee): - main([self.root_tmp, "--"]) + def test_single_directory_no_pytest_args(self, watch_callee, tmpdir): + root_tmp = str(tmpdir) + + main([root_tmp, "--"]) assert "pytest_args" in watch_callee.call_args[1] pytest_args = watch_callee.call_args[1]["pytest_args"] assert len(pytest_args) > 0 - assert [self.root_tmp] == pytest_args + assert [root_tmp] == pytest_args assert 1 == watch_callee.call_count fetched_directories = watch_callee.call_args[1]["directories"] - assert [self.root_tmp] == fetched_directories + assert [root_tmp] == fetched_directories - def test_single_directory_single_pytest_args(self, watch_callee): - vargs = [self.root_tmp, "--", "--pdb"] + def test_single_directory_single_pytest_args(self, watch_callee, tmpdir): + root_tmp = str(tmpdir) + vargs = [root_tmp, "--", "--pdb"] main(vargs) @@ -408,13 +398,14 @@ def test_single_directory_single_pytest_args(self, watch_callee): pytest_args = watch_callee.call_args[1]["pytest_args"] assert len(pytest_args) > 0 - assert [self.root_tmp, "--pdb"] == pytest_args + assert [root_tmp, "--pdb"] == pytest_args assert 1 == watch_callee.call_count - assert [self.root_tmp] == fetched_directories + assert [root_tmp] == fetched_directories - def test_single_directory_multiple_pytest_args(self, watch_callee): - vargs = [self.root_tmp, "--", "--pdb", "--cov=."] + def test_single_directory_multiple_pytest_args(self, watch_callee, tmpdir): + root_tmp = str(tmpdir) + vargs = [root_tmp, "--", "--pdb", "--cov=."] main(vargs) @@ -429,22 +420,16 @@ def test_single_directory_multiple_pytest_args(self, watch_callee): pytest_args = watch_callee.call_args[1]["pytest_args"] assert len(pytest_args) > 0 - assert [self.root_tmp, "--pdb", "--cov=."] == pytest_args + assert [root_tmp, "--pdb", "--cov=."] == pytest_args assert 1 == watch_callee.call_count - assert [self.root_tmp] == fetched_directories + assert [root_tmp] == fetched_directories +@pytest.mark.usefixtures("watch_callee", "tmpdir_factory") class TestDirectoriesArguments(): - def setup_method(self): - self.root_tmp = tempfile.mkdtemp() - - def teardown_method(self): - shutil.rmtree(self.root_tmp) - - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) def test_default_directories(self, watch_callee): directories = [] @@ -454,23 +439,22 @@ def test_default_directories(self, watch_callee): fetched_directories = watch_callee.call_args[1]["directories"] assert directories == fetched_directories - assert 1 == watch_callee.call_count - def test_single_directory(self): - directories = [self.root_tmp] - self._assert_directories(directories) + def test_single_directory(self, watch_callee, tmpdir): + root_tmp = str(tmpdir) + directories = [root_tmp] + self._assert_directories(directories, watch_callee) - def test_two_directory_values(self): - directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(2)] - self._assert_directories(directories) + def test_two_directory_values(self, tmpdir_factory, watch_callee): + directories = [str(tmpdir_factory.mktemp("_")) for _ in range(2)] + self._assert_directories(directories, watch_callee) - def test_ten_directory_values(self): - directories = [tempfile.mkdtemp(dir=self.root_tmp) for _ in range(10)] - self._assert_directories(directories) + def test_ten_directory_values(self, tmpdir_factory, watch_callee): + directories = [str(tmpdir_factory.mktemp("_")) for _ in range(10)] + self._assert_directories(directories, watch_callee) - @patch("pytest_watch.command.watch", side_effect=lambda *args, **kwargs: 0) - def _assert_directories(self, directories, watch_callee=None): + def _assert_directories(self, directories, watch_callee): assert len(directories) > 0, \ "Multiple directories should be declared for this test case" From 2e250cb0f4d9cc92b38faf79d7c268221a9a63e9 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Fri, 23 Feb 2018 18:45:07 -0300 Subject: [PATCH 69/93] Added pytest-mock dependency to setup.py & requirements-test.txt --- requirements-test.txt | 1 + setup.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index ed93332..ab3f3e9 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1 +1,2 @@ pytest-cov>=2.5.1 +pytest-mock>=1.7.0 diff --git a/setup.py b/setup.py index b073c5e..86d7370 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def read(filename): DEPS_MAIN = ["colorama>=0.3.3", "docopt>=0.6.2", "pytest>=2.6.4", "watchdog>=0.6.0"] DEPS_QA = ["pytest-cov>=2.5.1"] -DEPS_TESTING = [] +DEPS_TESTING = ["pytest-mock>=1.7.0"] setup( From 8d699d57e980eadaa019fbb4ad99e718cf81bc10 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Fri, 23 Feb 2018 18:49:12 -0300 Subject: [PATCH 70/93] Added DEPS_TESTING to DEPS_QA --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 86d7370..59bb742 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,8 @@ def read(filename): DEPS_MAIN = ["colorama>=0.3.3", "docopt>=0.6.2", "pytest>=2.6.4", "watchdog>=0.6.0"] -DEPS_QA = ["pytest-cov>=2.5.1"] DEPS_TESTING = ["pytest-mock>=1.7.0"] +DEPS_QA = DEPS_TESTING + ["pytest-cov>=2.5.1"] setup( From 77e529bd127ed1cbb53919d6065b67241f54bce6 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Fri, 23 Feb 2018 19:09:03 -0300 Subject: [PATCH 71/93] Added codecov.com support to Travis-CI --- .coveragerc | 2 ++ .travis.yml | 3 +++ setup.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..d5572d0 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +source=pytest_watch diff --git a/.travis.yml b/.travis.yml index 3179fd9..88d7b27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,3 +17,6 @@ install: script: - python -m pytest --cov=pytest_watch pytest_watch + +after_success: + - codecov --token=$CODECOV_TOKEN diff --git a/setup.py b/setup.py index 59bb742..1073b9c 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def read(filename): DEPS_MAIN = ["colorama>=0.3.3", "docopt>=0.6.2", "pytest>=2.6.4", "watchdog>=0.6.0"] DEPS_TESTING = ["pytest-mock>=1.7.0"] -DEPS_QA = DEPS_TESTING + ["pytest-cov>=2.5.1"] +DEPS_QA = DEPS_TESTING + ["pytest-cov>=2.5.1", "codecov"] setup( From 47adfc3d2b32fd3baee5a5e4d43347ed00dad0f6 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Fri, 23 Feb 2018 19:35:21 -0300 Subject: [PATCH 72/93] Add -v verbosity to travis-ci test coverage --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 88d7b27..c639c4d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: - pip install -e ".[qa]" script: -- python -m pytest --cov=pytest_watch pytest_watch +- python -m pytest --cov=pytest_watch --cov-report=term-missing -v pytest_watch after_success: - codecov --token=$CODECOV_TOKEN From bbe37af32a652708ed31e16172bb96be5a81fdc5 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Fri, 23 Feb 2018 19:36:56 -0300 Subject: [PATCH 73/93] split summary output definitions from core watcher module. --- pytest_watch/summary.py | 81 +++++++++++++++++++++++++++++++++++++++++ pytest_watch/watcher.py | 80 +--------------------------------------- 2 files changed, 83 insertions(+), 78 deletions(-) create mode 100644 pytest_watch/summary.py diff --git a/pytest_watch/summary.py b/pytest_watch/summary.py new file mode 100644 index 0000000..dee4d31 --- /dev/null +++ b/pytest_watch/summary.py @@ -0,0 +1,81 @@ +import time + +from colorama import Fore, Style + + +STYLE_BRIGHT = Fore.WHITE + Style.NORMAL + Style.BRIGHT +STYLE_HIGHLIGHT = Fore.CYAN + Style.NORMAL + Style.BRIGHT + + +def _reduce_events(events): + # FUTURE: Reduce ['a -> b', 'b -> c'] renames to ['a -> c'] + + creates = [] + moves = [] + for event, src, dest in events: + if event == FileCreatedEvent: + creates.append(dest) + if event == FileMovedEvent: + moves.append(dest) + + seen = [] + filtered = [] + for event, src, dest in events: + # Skip 'modified' event during 'created' + if src in creates and event != FileCreatedEvent: + continue + + # Skip 'modified' event during 'moved' + if src in moves: + continue + + # Skip duplicate events + if src in seen: + continue + seen.append(src) + + filtered.append((event, src, dest)) + return filtered + + +def _bright(arg): + return STYLE_BRIGHT + arg + Style.RESET_ALL + + +def _highlight(arg): + return STYLE_HIGHLIGHT + arg + Style.RESET_ALL + + +def show_summary(argv, events, verbose=False): + command = ' '.join(argv) + bright = _bright + highlight = _highlight + + time_stamp = time.strftime("%c", time.localtime(time.time())) + run_command_info = '[{}] Running: {}'.format(time_stamp, + highlight(command)) + if not events: + print(run_command_info) + return + + events = _reduce_events(events) + if verbose: + lines = ['Changes detected:'] + m = max(map(len, map(lambda e: VERBOSE_EVENT_NAMES[e[0]], events))) + for event, src, dest in events: + event = VERBOSE_EVENT_NAMES[event].ljust(m) + lines.append(' {} {}'.format( + event, + highlight(src + (' -> ' + dest if dest else '')))) + lines.append('') + lines.append(run_command_info) + else: + lines = [] + for event, src, dest in events: + lines.append('{} detected: {}'.format( + EVENT_NAMES[event], + bright(src + (' -> ' + dest if dest else '')))) + lines.append('') + lines.append(run_command_info) + + print('\n'.join(lines)) diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index b497417..ba00e3a 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -11,7 +11,6 @@ except ImportError: from Queue import Queue -from colorama import Fore, Style from watchdog.events import ( FileSystemEventHandler, FileModifiedEvent, FileCreatedEvent, FileMovedEvent, FileDeletedEvent) @@ -22,6 +21,7 @@ ALL_EXTENSIONS, EXIT_NOTESTSCOLLECTED, EXIT_OK, DEFAULT_EXTENSIONS) from .helpers import ( beep, clear, dequeue_all, is_windows, samepath, send_keyboard_interrupt) +from .summary import show_summary EVENT_NAMES = { @@ -37,8 +37,6 @@ FileDeletedEvent: 'Deleted:', } WATCHED_EVENTS = tuple(EVENT_NAMES) -STYLE_BRIGHT = Fore.WHITE + Style.NORMAL + Style.BRIGHT -STYLE_HIGHLIGHT = Fore.CYAN + Style.NORMAL + Style.BRIGHT class EventListener(FileSystemEventHandler): @@ -88,80 +86,6 @@ def _get_pytest_runner(custom=None): return [sys.executable, '-m', 'pytest'] -def _reduce_events(events): - # FUTURE: Reduce ['a -> b', 'b -> c'] renames to ['a -> c'] - - creates = [] - moves = [] - for event, src, dest in events: - if event == FileCreatedEvent: - creates.append(dest) - if event == FileMovedEvent: - moves.append(dest) - - seen = [] - filtered = [] - for event, src, dest in events: - # Skip 'modified' event during 'created' - if src in creates and event != FileCreatedEvent: - continue - - # Skip 'modified' event during 'moved' - if src in moves: - continue - - # Skip duplicate events - if src in seen: - continue - seen.append(src) - - filtered.append((event, src, dest)) - return filtered - - -def _bright(arg): - return STYLE_BRIGHT + arg + Style.RESET_ALL - - -def _highlight(arg): - return STYLE_HIGHLIGHT + arg + Style.RESET_ALL - - -def _show_summary(argv, events, verbose=False): - command = ' '.join(argv) - bright = _bright - highlight = _highlight - - time_stamp = time.strftime("%c", time.localtime(time.time())) - run_command_info = '[{}] Running: {}'.format(time_stamp, - highlight(command)) - if not events: - print(run_command_info) - return - - events = _reduce_events(events) - if verbose: - lines = ['Changes detected:'] - m = max(map(len, map(lambda e: VERBOSE_EVENT_NAMES[e[0]], events))) - for event, src, dest in events: - event = VERBOSE_EVENT_NAMES[event].ljust(m) - lines.append(' {} {}'.format( - event, - highlight(src + (' -> ' + dest if dest else '')))) - lines.append('') - lines.append(run_command_info) - else: - lines = [] - for event, src, dest in events: - lines.append('{} detected: {}'.format( - EVENT_NAMES[event], - bright(src + (' -> ' + dest if dest else '')))) - lines.append('') - lines.append(run_command_info) - - print('\n'.join(lines)) - - def _split_recursive(directories, ignore): if not ignore: @@ -252,7 +176,7 @@ def watch(directories=None, ignore=None, extensions=None, beep_on_failure=True, # Show event summary if not quiet: - _show_summary(argv, events, verbose) + show_summary(argv, events, verbose) # Run custom command run_hook(beforerun) From 1f597c84a49ff40d36e83fa77c1f9f2134f6890a Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 25 Feb 2018 17:31:19 -0300 Subject: [PATCH 74/93] For tests/mock purposes, ctype was moved to module level import section. --- pytest_watch/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_watch/helpers.py b/pytest_watch/helpers.py index 06b0d16..d1a4ba8 100644 --- a/pytest_watch/helpers.py +++ b/pytest_watch/helpers.py @@ -1,3 +1,4 @@ +import ctypes import os import signal import subprocess @@ -70,7 +71,6 @@ def send_keyboard_interrupt(proc): os.kill(0, signal.CTRL_C_EVENT) except AttributeError: # Python 2.6 and below - import ctypes ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, 0) # Immediately throws KeyboardInterrupt from the simulated CTRL-C proc.wait() From e39858e1307e5a61dfea883d51c9c658256f0ce9 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 25 Feb 2018 17:32:07 -0300 Subject: [PATCH 75/93] Added tests for helpers.send_keyboard_event, covering is_windows (True|False) cases and legacy python versions --- pytest_watch/tests/test_helpers.py | 82 ++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 pytest_watch/tests/test_helpers.py diff --git a/pytest_watch/tests/test_helpers.py b/pytest_watch/tests/test_helpers.py new file mode 100644 index 0000000..86d30fa --- /dev/null +++ b/pytest_watch/tests/test_helpers.py @@ -0,0 +1,82 @@ +import signal +import subprocess +import sys + +import pytest +from pytest_watch import helpers + + +@pytest.fixture +def windows_ctrlc_mock(mocker): + k32_mock = mocker.patch("pytest_watch.helpers.ctypes") + ctrlc_mock = mocker.patch.object(k32_mock.windll.kernel32, "GenerateConsoleCtrlEvent") + return ctrlc_mock + + +@pytest.fixture +def python_version_proc(): + return subprocess.Popen(f"{sys.executable}", + shell=helpers.is_windows) + + +def test_linux_process_kill_is_called(mocker, python_version_proc): + is_windows = mocker.patch.dict("pytest_watch.helpers.__dict__", + {"is_windows": False}) + + os_mock = mocker.patch("pytest_watch.helpers.os") + + kill_mock = mocker.patch.object(os_mock, "kill", + side_effect=lambda pid, s: pid) + + with python_version_proc as proc, kill_mock: + helpers.send_keyboard_interrupt(proc) + + assert 1 == kill_mock.call_count + assert (proc.pid, signal.SIGINT) == kill_mock.call_args[0] + + +def test_windows_process_kill_for_python26upper_is_called(mocker, + python_version_proc, + windows_ctrlc_mock): + ctrl_c_code = signal.SIGINT + + is_windows = mocker.patch.dict("pytest_watch.helpers.__dict__", + {"is_windows": True}) + ctrl_c_event = mocker.patch.dict("pytest_watch.helpers.signal.__dict__", + {"CTRL_C_EVENT": ctrl_c_code}) + + os_mock = mocker.patch("pytest_watch.helpers.os") + + kill_mock = mocker.patch.object(os_mock, "kill", + side_effect=lambda pid, s: pid) + + with kill_mock, windows_ctrlc_mock, python_version_proc as proc: + mocker.patch.object(proc, "wait", side_effect=print) + helpers.send_keyboard_interrupt(proc) + + assert 0 == windows_ctrlc_mock.call_count + assert 1 == kill_mock.call_count + assert (0, ctrl_c_code) == kill_mock.call_args[0] + + +def test_windows_process_kill_for_python26_is_called(mocker, + windows_ctrlc_mock, + python_version_proc): + ctrl_c_code = signal.SIGINT + + is_windows = mocker.patch.dict("pytest_watch.helpers.__dict__", + {"is_windows": True}) + ctrl_c_event = mocker.patch.dict("pytest_watch.helpers.signal.__dict__", + {"CTRL_C_EVENT": ctrl_c_code}) + + os_mock = mocker.patch("pytest_watch.helpers.os") + + kill_mock = mocker.patch.object(os_mock, "kill", + side_effect=AttributeError) + + with kill_mock, windows_ctrlc_mock, python_version_proc as proc: + mocker.patch.object(proc, "wait", side_effect=print) + helpers.send_keyboard_interrupt(proc) + + assert 1 == windows_ctrlc_mock.call_count + assert (0, 0) == windows_ctrlc_mock.call_args[0] From 3c8ff1bf668147619fadccec81d2e96a92be4e18 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 25 Feb 2018 17:36:24 -0300 Subject: [PATCH 76/93] Compatibility with 3- versions. Format string was introduced. Now, rollback --- pytest_watch/tests/test_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_watch/tests/test_helpers.py b/pytest_watch/tests/test_helpers.py index 86d30fa..90a94a7 100644 --- a/pytest_watch/tests/test_helpers.py +++ b/pytest_watch/tests/test_helpers.py @@ -15,7 +15,7 @@ def windows_ctrlc_mock(mocker): @pytest.fixture def python_version_proc(): - return subprocess.Popen(f"{sys.executable}", + return subprocess.Popen(sys.executable, shell=helpers.is_windows) From 1f8888f471d14788c5f6db41af9382f4c468e387 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 25 Feb 2018 17:58:25 -0300 Subject: [PATCH 77/93] Fix test_helpers for python 2.7 compatibility and some simplifications --- pytest_watch/tests/test_helpers.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pytest_watch/tests/test_helpers.py b/pytest_watch/tests/test_helpers.py index 90a94a7..8c1ee78 100644 --- a/pytest_watch/tests/test_helpers.py +++ b/pytest_watch/tests/test_helpers.py @@ -28,11 +28,10 @@ def test_linux_process_kill_is_called(mocker, python_version_proc): kill_mock = mocker.patch.object(os_mock, "kill", side_effect=lambda pid, s: pid) - with python_version_proc as proc, kill_mock: - helpers.send_keyboard_interrupt(proc) + helpers.send_keyboard_interrupt(python_version_proc) assert 1 == kill_mock.call_count - assert (proc.pid, signal.SIGINT) == kill_mock.call_args[0] + assert (python_version_proc.pid, signal.SIGINT) == kill_mock.call_args[0] def test_windows_process_kill_for_python26upper_is_called(mocker, @@ -50,9 +49,8 @@ def test_windows_process_kill_for_python26upper_is_called(mocker, kill_mock = mocker.patch.object(os_mock, "kill", side_effect=lambda pid, s: pid) - with kill_mock, windows_ctrlc_mock, python_version_proc as proc: - mocker.patch.object(proc, "wait", side_effect=print) - helpers.send_keyboard_interrupt(proc) + mocker.patch.object(python_version_proc, "wait") + helpers.send_keyboard_interrupt(python_version_proc) assert 0 == windows_ctrlc_mock.call_count assert 1 == kill_mock.call_count @@ -74,9 +72,8 @@ def test_windows_process_kill_for_python26_is_called(mocker, kill_mock = mocker.patch.object(os_mock, "kill", side_effect=AttributeError) - with kill_mock, windows_ctrlc_mock, python_version_proc as proc: - mocker.patch.object(proc, "wait", side_effect=print) - helpers.send_keyboard_interrupt(proc) + mocker.patch.object(python_version_proc, "wait") + helpers.send_keyboard_interrupt(python_version_proc) assert 1 == windows_ctrlc_mock.call_count assert (0, 0) == windows_ctrlc_mock.call_args[0] From d5eefb9e9b133c03b005e72582a7659aca2f3ec3 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 25 Feb 2018 18:06:09 -0300 Subject: [PATCH 78/93] Fix test_helpers for python 2.7 compatibility and some simplifications --- pytest_watch/tests/test_helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytest_watch/tests/test_helpers.py b/pytest_watch/tests/test_helpers.py index 8c1ee78..6c78bbb 100644 --- a/pytest_watch/tests/test_helpers.py +++ b/pytest_watch/tests/test_helpers.py @@ -9,7 +9,8 @@ @pytest.fixture def windows_ctrlc_mock(mocker): k32_mock = mocker.patch("pytest_watch.helpers.ctypes") - ctrlc_mock = mocker.patch.object(k32_mock.windll.kernel32, "GenerateConsoleCtrlEvent") + ctrlc_mock = mocker.patch.object(k32_mock.windll.kernel32, + "GenerateConsoleCtrlEvent") return ctrlc_mock From 3932987040bc59ce234b3049e42cf370416b61d6 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 25 Feb 2018 18:06:49 -0300 Subject: [PATCH 79/93] pep8 updates --- pytest_watch/tests/conftest.py | 2 +- pytest_watch/tests/test_command.py | 10 ++++------ pytest_watch/tests/test_watcher.py | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pytest_watch/tests/conftest.py b/pytest_watch/tests/conftest.py index ade5818..921b57e 100644 --- a/pytest_watch/tests/conftest.py +++ b/pytest_watch/tests/conftest.py @@ -11,5 +11,5 @@ def merge_config_callee(mocker): @pytest.fixture def watch_callee(mocker): watch_mock = mocker.patch("pytest_watch.command.watch") - watch_mock.return_value.side_effect=lambda *args, **kwargs: 0 + watch_mock.return_value.side_effect = lambda *args, **kwargs: 0 return watch_mock diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index b39e41b..a594b7f 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -124,7 +124,6 @@ def _assert_config_file(self, watch_callee, filename): assert filename == pytest_args[pytest_args.index("-c")+1] - @pytest.mark.usefixtures("watch_callee", "tmpdir_factory") class TestIgnoreArgument(): def test_default_ignore_argument(self, watch_callee): @@ -136,7 +135,6 @@ def test_default_ignore_argument(self, watch_callee): assert "--ignore" not in watch_callee.call_args[1]["pytest_args"] - def test_ignore_argument(self, watch_callee): main(["--ignore", "pytest_watch"]) @@ -144,7 +142,6 @@ def test_ignore_argument(self, watch_callee): assert "--ignore" in watch_callee.call_args[1]["pytest_args"] - def test_multiple_ignore_argument(self, tmpdir_factory, watch_callee): directories = [] argv = [] @@ -164,8 +161,8 @@ def test_multiple_ignore_argument(self, tmpdir_factory, watch_callee): ignore_idx = pytest_args.index("--ignore") assert argv == pytest_args - - def test_multiple_ignore_argument_conflict(self, tmpdir_factory, watch_callee): + def test_multiple_ignore_argument_conflict(self, tmpdir_factory, + watch_callee): directories = [] argv = [] @@ -347,7 +344,8 @@ def test_no_directory_multiple_pytest_args(self, watch_callee): assert 1 == watch_callee.call_count - def test_multiple_directory_no_pytest_args(self, tmpdir_factory, watch_callee): + def test_multiple_directory_no_pytest_args(self, tmpdir_factory, + watch_callee): directories = [str(tmpdir_factory.mktemp("_")) for _ in range(2)] directories.append("--") diff --git a/pytest_watch/tests/test_watcher.py b/pytest_watch/tests/test_watcher.py index 51e8e2b..2f6a2f5 100644 --- a/pytest_watch/tests/test_watcher.py +++ b/pytest_watch/tests/test_watcher.py @@ -87,7 +87,7 @@ def test_ignore_subdirs_partially(self): assert ([included_folder], [self.root_dir]) == result, fail_msg @skip("Depends on pytest_watch.watcher._split_recursive support" - " for deep recursive navigation through directory tree") + " for deep recursive navigation through directory tree") def test_ignore_deep_subtree_multichild(self): """ This test runs over the following tree structure: @@ -116,7 +116,7 @@ def test_ignore_deep_subtree_multichild(self): ignore) @skip("Depends on pytest_watch.watcher._split_recursive support" - " for deep recursive navigation through directory tree") + " for deep recursive navigation through directory tree") def test_ignore_deep_subtree_single(self): """ This test runs over the following tree structure: From f62cd37ec4f32c3507a320dc0a4617040c624bb7 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 25 Feb 2018 18:08:05 -0300 Subject: [PATCH 80/93] Remove conflicting pdbpp and pytest-pdb. It should be analysed why there are issues when they are on environment --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1073b9c..4561429 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def read(filename): }, extras_require={ 'testing': DEPS_TESTING, - 'dev': DEPS_TESTING + DEPS_QA + ['pdbpp', 'pytest-pdb'], + 'dev': DEPS_TESTING + DEPS_QA, 'qa': DEPS_QA, 'testing:python_version in "2.6, 2.7, 3.2"': ['mock'], 'dev:python_version in "2.6, 2.7, 3.2"': ['mock'], From 1f11440851d052506d35b5ecd3a8524068eae3e0 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 25 Feb 2018 22:03:23 -0300 Subject: [PATCH 81/93] Added helpers.clear and helpers.beep tests & fixtures --- pytest_watch/tests/conftest.py | 5 +++++ pytest_watch/tests/test_helpers.py | 28 +++++++++++++++++++++++++++- setup.py | 2 +- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pytest_watch/tests/conftest.py b/pytest_watch/tests/conftest.py index 921b57e..e04ba45 100644 --- a/pytest_watch/tests/conftest.py +++ b/pytest_watch/tests/conftest.py @@ -8,6 +8,11 @@ def merge_config_callee(mocker): return m +@pytest.fixture +def beep_mock(mocker): + return mocker.patch("pytest_watch.helpers.beep") + + @pytest.fixture def watch_callee(mocker): watch_mock = mocker.patch("pytest_watch.command.watch") diff --git a/pytest_watch/tests/test_helpers.py b/pytest_watch/tests/test_helpers.py index 6c78bbb..0b84a7d 100644 --- a/pytest_watch/tests/test_helpers.py +++ b/pytest_watch/tests/test_helpers.py @@ -20,6 +20,32 @@ def python_version_proc(): shell=helpers.is_windows) +def test_linux_clear_with_clear_command(mocker): + is_windows = mocker.patch.dict("pytest_watch.helpers.__dict__", + {"is_windows": False}) + + call_mock = mocker.patch("pytest_watch.helpers.subprocess.call") + + helpers.clear() + + assert 1 == call_mock.call_count + assert ("clear",) == call_mock.call_args[0] + assert dict(shell=True) == call_mock.call_args[1] + + +def test_windows_clear_with_cls_command(mocker): + is_windows = mocker.patch.dict("pytest_watch.helpers.__dict__", + {"is_windows": True}) + + call_mock = mocker.patch("pytest_watch.helpers.subprocess.call") + + helpers.clear() + + assert 1 == call_mock.call_count + assert ("cls",) == call_mock.call_args[0] + assert dict(shell=True) == call_mock.call_args[1] + + def test_linux_process_kill_is_called(mocker, python_version_proc): is_windows = mocker.patch.dict("pytest_watch.helpers.__dict__", {"is_windows": False}) @@ -48,7 +74,7 @@ def test_windows_process_kill_for_python26upper_is_called(mocker, os_mock = mocker.patch("pytest_watch.helpers.os") kill_mock = mocker.patch.object(os_mock, "kill", - side_effect=lambda pid, s: pid) + side_effect=KeyboardInterrupt) mocker.patch.object(python_version_proc, "wait") helpers.send_keyboard_interrupt(python_version_proc) diff --git a/setup.py b/setup.py index 4561429..5c3f2b6 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def read(filename): DEPS_MAIN = ["colorama>=0.3.3", "docopt>=0.6.2", "pytest>=2.6.4", "watchdog>=0.6.0"] DEPS_TESTING = ["pytest-mock>=1.7.0"] -DEPS_QA = DEPS_TESTING + ["pytest-cov>=2.5.1", "codecov"] +DEPS_QA = DEPS_TESTING + ["pytest-cov>=2.5.1", "codecov", "pytest-pep8"] setup( From 828c6ebc25d1e9fe43957644cd1fe9cbad4756ba Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 25 Feb 2018 23:49:11 -0300 Subject: [PATCH 82/93] Added pytest_watch.util test, covering Exception handling --- pytest_watch/tests/test_util.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 pytest_watch/tests/test_util.py diff --git a/pytest_watch/tests/test_util.py b/pytest_watch/tests/test_util.py new file mode 100644 index 0000000..c46a72e --- /dev/null +++ b/pytest_watch/tests/test_util.py @@ -0,0 +1,10 @@ +import pytest + +from pytest_watch.util import silence + + +def test_handle_exception(): + with pytest.raises(Exception) as ex: + with silence(): + raise ValueError() + assert ex.errisinstance(ValueError) From 299067ff56bca0ee176f8cb1661a5a9b01729dc0 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Sun, 25 Feb 2018 23:49:26 -0300 Subject: [PATCH 83/93] Added pytest_watch.util test, covering Exception handling --- pytest_watch/tests/test_util.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pytest_watch/tests/test_util.py b/pytest_watch/tests/test_util.py index c46a72e..caa335d 100644 --- a/pytest_watch/tests/test_util.py +++ b/pytest_watch/tests/test_util.py @@ -3,8 +3,15 @@ from pytest_watch.util import silence -def test_handle_exception(): - with pytest.raises(Exception) as ex: +def test_handle_exception_type(): + with pytest.raises(ValueError) as ex: with silence(): - raise ValueError() + raise ValueError("Custom message error") assert ex.errisinstance(ValueError) + + +def test_handle_exception_message(): + with pytest.raises(KeyError) as ex: + with silence(): + raise KeyError("Custom message error") + assert ex.match("Custom message error") From e6b26b86cb3ddf49489c7e3f0d728ca9ea1415b6 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Mon, 26 Feb 2018 10:08:44 -0300 Subject: [PATCH 84/93] Add tests for pytest_watch.helpers samepath & deque_all --- pytest_watch/helpers.py | 7 ++- pytest_watch/tests/test_helpers.py | 81 ++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/pytest_watch/helpers.py b/pytest_watch/helpers.py index d1a4ba8..87d14e9 100644 --- a/pytest_watch/helpers.py +++ b/pytest_watch/helpers.py @@ -52,12 +52,15 @@ def dequeue_all(queue, spool=None): return items +def canonize_path(path): + return os.path.realpath(path) + + def samepath(left, right): """ Determines whether two paths are the same based on their absolute paths. """ - return (os.path.abspath(os.path.normcase(left)) == - os.path.abspath(os.path.normcase(right))) + return canonize_path(left) == canonize_path(right) def send_keyboard_interrupt(proc): diff --git a/pytest_watch/tests/test_helpers.py b/pytest_watch/tests/test_helpers.py index 0b84a7d..e507044 100644 --- a/pytest_watch/tests/test_helpers.py +++ b/pytest_watch/tests/test_helpers.py @@ -1,6 +1,13 @@ +import os import signal import subprocess import sys +from time import sleep + +try: + from queue import Queue +except ImportError: + from Queue import Queue import pytest from pytest_watch import helpers @@ -104,3 +111,77 @@ def test_windows_process_kill_for_python26_is_called(mocker, assert 1 == windows_ctrlc_mock.call_count assert (0, 0) == windows_ctrlc_mock.call_args[0] + + +def test_dequeall_from_an_empty_queue_with_no_spool(): + q = Queue() + assert [] == helpers.dequeue_all(q, 0) + + +def test_dequeall_from_a_single_queue_with_no_spool(): + q = Queue() + q.put("element 1") + assert ["element 1"] == helpers.dequeue_all(q, 0) + + +def test_dequeall_from_multi_queue_with_no_spool(): + q = Queue() + q.put("element 1") + assert ["element 1"] == helpers.dequeue_all(q, 0) + q.put("element 2") + q.put("element 3") + assert ["element 2", "element 3"] == helpers.dequeue_all(q, 0) + + +def test_dequeall_from_multi_with_spool_200(mocker): + def _is_first_empty(): + empty = False + yield empty + + sleep_mock = mocker.patch("pytest_watch.helpers.sleep", wrap=sleep) + q = Queue() + mocker.patch.object(q, "empty", side_effect=_is_first_empty) + q.put("element 1") + q.put("element 2") + dequeued = helpers.dequeue_all(q) + sleep(.3) + q.put("element 3") + assert (.2,) == sleep_mock.call_args[0] + assert ["element 1", "element 2"] == dequeued + assert ["element 3"] == helpers.dequeue_all(q, 0) + + +def test_samepath_for_non_existent_file_without_errors(tmpdir): + samedir = tmpdir.mkdir("samepath") + file1 = samedir.join("file1.txt") + with open(file1.strpath, "w") as f: + f.write(".") + file2 = samedir.join("inexistent.txt") + + assert file1.exists() + assert not file2.exists() + assert not helpers.samepath(file1.strpath, file2.strpath) + + +def test_samepath_for_symbolic_link(tmpdir): + samedir = tmpdir.mkdir("samepath") + file1 = samedir.join("file1.txt") + with open(file1.strpath, "w") as f: + f.write(".") + symlink = samedir.join("symlink1.txt") + symlink.mksymlinkto(file1) + + assert os.path.islink(symlink.strpath) + assert helpers.samepath(file1.strpath, symlink.strpath) + + +def test_samepath_for_same_file(tmpdir): + samedir = tmpdir.mkdir("samepath") + file1 = samedir.join("file1.txt") + assert helpers.samepath(file1.strpath, file1.strpath) + + +def test_samepath_fail_for_different_absolute_path(tmpdir): + samedir = tmpdir.mkdir("samepath") + assert not helpers.samepath(samedir.join("file1.txt").strpath, + samedir.join("file2.txt").strpath) From 5f2cf64e1a9db968d55db88d7dfd15d62bbc4cc1 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Mon, 26 Feb 2018 10:13:07 -0300 Subject: [PATCH 85/93] Added test for named space files on samepath --- pytest_watch/tests/test_helpers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pytest_watch/tests/test_helpers.py b/pytest_watch/tests/test_helpers.py index e507044..f9e307f 100644 --- a/pytest_watch/tests/test_helpers.py +++ b/pytest_watch/tests/test_helpers.py @@ -163,6 +163,18 @@ def test_samepath_for_non_existent_file_without_errors(tmpdir): assert not helpers.samepath(file1.strpath, file2.strpath) +def test_samepath_for_name_spaced_symbolic_link(tmpdir): + samedir = tmpdir.mkdir("samepath") + file1 = samedir.join("file1.txt") + with open(file1.strpath, "w") as f: + f.write(".") + symlink = samedir.join("Symbolic Link.txt") + symlink.mksymlinkto(file1) + + assert os.path.islink(symlink.strpath) + assert helpers.samepath(file1.strpath, symlink.strpath) + + def test_samepath_for_symbolic_link(tmpdir): samedir = tmpdir.mkdir("samepath") file1 = samedir.join("file1.txt") From 03c243bf8331b351f4ce869d2778692e201351a1 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 8 Mar 2018 22:35:16 -0300 Subject: [PATCH 86/93] gitignore coverage.xml --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7e1a2c9..a363305 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ env .DS_Store Desktop.ini Thumbs.db +coverage.xml From 2ef5b862fd688f1342026c6ba85b51457086ee70 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Fri, 9 Mar 2018 09:34:24 -0300 Subject: [PATCH 87/93] Added pytest-runner to setup.py and 'setup.py test' alias for 'pytest' --- setup.cfg | 2 ++ setup.py | 1 + 2 files changed, 3 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..b7e4789 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/setup.py b/setup.py index 5c3f2b6..6d76dea 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ def read(filename): platforms='any', packages=find_packages(), install_requires=DEPS_MAIN, + setup_requires=['pytest-runner',], tests_require=DEPS_TESTING, entry_points={ 'console_scripts': [ From 42a64da50da3ac7ae9a1a66271f4ef604ed8c21f Mon Sep 17 00:00:00 2001 From: And Past Date: Mon, 12 Mar 2018 08:51:01 -0300 Subject: [PATCH 88/93] Skip tests on win32 system for checking real path for symlinks. Win32 doesnt support symlinks --- pytest_watch/tests/test_command.py | 6 ++++-- pytest_watch/tests/test_helpers.py | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pytest_watch/tests/test_command.py b/pytest_watch/tests/test_command.py index a594b7f..da697a0 100644 --- a/pytest_watch/tests/test_command.py +++ b/pytest_watch/tests/test_command.py @@ -316,7 +316,7 @@ def test_multiple_with_and_without_dots_extensions(self, watch_callee): assert 1 == watch_callee.call_count -@pytest.mark.usefixtures("watch_callee", "tmpdir") +@pytest.mark.usefixtures("watch_callee", "tmpdir", "merge_config_callee") class TestDirectoriesAndPytestArgsArgumentsSplit(): def test_no_directory_empty_pytest_arg(self, watch_callee): @@ -335,8 +335,10 @@ def test_no_directory_single_pytest_arg(self, watch_callee): assert 1 == watch_callee.call_count - def test_no_directory_multiple_pytest_args(self, watch_callee): + def test_no_directory_multiple_pytest_args(self, watch_callee, merge_config_callee): main("-- --pdb --cov=.".split()) + + assert 1 == merge_config_callee.call_count assert "pytest_args" in watch_callee.call_args[1] diff --git a/pytest_watch/tests/test_helpers.py b/pytest_watch/tests/test_helpers.py index f9e307f..aeaee35 100644 --- a/pytest_watch/tests/test_helpers.py +++ b/pytest_watch/tests/test_helpers.py @@ -163,6 +163,8 @@ def test_samepath_for_non_existent_file_without_errors(tmpdir): assert not helpers.samepath(file1.strpath, file2.strpath) +@pytest.mark.skipif(sys.platform == 'win32', + reason="does not run on windows. System doesnt support symlinks") def test_samepath_for_name_spaced_symbolic_link(tmpdir): samedir = tmpdir.mkdir("samepath") file1 = samedir.join("file1.txt") @@ -175,6 +177,8 @@ def test_samepath_for_name_spaced_symbolic_link(tmpdir): assert helpers.samepath(file1.strpath, symlink.strpath) +@pytest.mark.skipif(sys.platform == 'win32', + reason="does not run on windows. System doesnt support symlinks") def test_samepath_for_symbolic_link(tmpdir): samedir = tmpdir.mkdir("samepath") file1 = samedir.join("file1.txt") From c944899d1748c41d6a605138b2321fd4dd351a16 Mon Sep 17 00:00:00 2001 From: And Past Date: Mon, 12 Mar 2018 08:52:12 -0300 Subject: [PATCH 89/93] wrapper for build win32 command line path --- pytest_watch/tests/test_watcher_hooks.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pytest_watch/tests/test_watcher_hooks.py b/pytest_watch/tests/test_watcher_hooks.py index a1d465b..31a9ce3 100644 --- a/pytest_watch/tests/test_watcher_hooks.py +++ b/pytest_watch/tests/test_watcher_hooks.py @@ -1,3 +1,4 @@ +import os import subprocess from subprocess import call as _subcall import sys @@ -10,6 +11,7 @@ import pytest from pytest_watch.constants import EXIT_NOTESTSCOLLECTED, EXIT_OK +from pytest_watch.helpers import is_windows from pytest_watch.watcher import watch, run_hook from pytest_watch.watcher import subprocess as wsubprocess from pytest_watch import watcher @@ -40,13 +42,20 @@ def _wrapped(*args, **kwargs): assert expected == callee(*args, **kwargs) return _wrapped + +def get_sys_path(p): + p = os.path.normpath(p) + if is_windows: + p = '"%s"'%p + return p + class TestRunHooksBasic(): @mock.patch("pytest_watch.watcher.subprocess.call", side_effect=assertion_wrapper(0, _subcall)) def test_run_hook_systemexit_0(self, call_mock): - python_exec = sys.executable + python_exec = get_sys_path(sys.executable) cmd_parts = [python_exec, "-c", "'exit(0)'"] cmd = " ".join(cmd_parts) run_hook(cmd) @@ -55,7 +64,7 @@ def test_run_hook_systemexit_0(self, call_mock): @mock.patch("pytest_watch.watcher.subprocess.call", side_effect=assertion_wrapper(1, _subcall)) def test_run_hook_systemexit_not_0(self, call_mock): - python_exec = sys.executable + python_exec = get_sys_path(sys.executable) cmd_parts = [python_exec, "-c", "'exit(1)'"] cmd = " ".join(cmd_parts) run_hook(cmd) From e0beb472e91823b90a526157fa604a3c7ad6e594 Mon Sep 17 00:00:00 2001 From: And Past Date: Sun, 18 Mar 2018 11:03:20 -0300 Subject: [PATCH 90/93] subprocess.call working for Windows paths --- pytest_watch/tests/test_watcher_hooks.py | 51 ++++++++++++++---------- pytest_watch/watcher.py | 7 +++- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/pytest_watch/tests/test_watcher_hooks.py b/pytest_watch/tests/test_watcher_hooks.py index 31a9ce3..65eb0bf 100644 --- a/pytest_watch/tests/test_watcher_hooks.py +++ b/pytest_watch/tests/test_watcher_hooks.py @@ -10,6 +10,7 @@ import pytest +import pytest_watch from pytest_watch.constants import EXIT_NOTESTSCOLLECTED, EXIT_OK from pytest_watch.helpers import is_windows from pytest_watch.watcher import watch, run_hook @@ -44,31 +45,41 @@ def _wrapped(*args, **kwargs): def get_sys_path(p): - p = os.path.normpath(p) + #p = os.path.normpath(p) if is_windows: p = '"%s"'%p return p -class TestRunHooksBasic(): - - @mock.patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(0, _subcall)) - def test_run_hook_systemexit_0(self, call_mock): - python_exec = get_sys_path(sys.executable) - cmd_parts = [python_exec, "-c", "'exit(0)'"] - cmd = " ".join(cmd_parts) - run_hook(cmd) - call_mock.assert_called_once_with(cmd, shell=True) - - @mock.patch("pytest_watch.watcher.subprocess.call", - side_effect=assertion_wrapper(1, _subcall)) - def test_run_hook_systemexit_not_0(self, call_mock): - python_exec = get_sys_path(sys.executable) - cmd_parts = [python_exec, "-c", "'exit(1)'"] - cmd = " ".join(cmd_parts) - run_hook(cmd) - call_mock.assert_called_once_with(cmd, shell=True) +@pytest.fixture +def subp_call_mock(mocker): + return mocker.patch.object(pytest_watch.watcher.subprocess, "call") + + +def test_run_hook_systemexit_0(subp_call_mock): + subp_call_mock.side_effect = side_effect=assertion_wrapper(0, _subcall) + + python_exec = get_sys_path(sys.executable) + cmd_parts = [python_exec, "-c", "'exit(0)'"] + cmd = " ".join(cmd_parts) + run_hook(cmd) + + assert 1 == subp_call_mock.call_count + assert (cmd,) == subp_call_mock.call_args[0] + assert dict(shell=True) == subp_call_mock.call_args[1] + + +def test_run_hook_systemexit_not_0(subp_call_mock): + subp_call_mock.side_effect = side_effect=assertion_wrapper(1, _subcall) + + python_exec = get_sys_path(sys.executable) + cmd_parts = [python_exec, "-c", "'raise Exception(\'force error\')'"] + cmd = " ".join(cmd_parts) + run_hook(cmd) + + assert 1 == subp_call_mock.call_count + assert (cmd,) == subp_call_mock.call_args[0] + assert dict(shell=True) == subp_call_mock.call_args[1] orig = watcher.run_hook diff --git a/pytest_watch/watcher.py b/pytest_watch/watcher.py index ba00e3a..e180d5b 100644 --- a/pytest_watch/watcher.py +++ b/pytest_watch/watcher.py @@ -124,7 +124,12 @@ def _split_recursive(directories, ignore): def run_hook(cmd, *args): """ - Runs a command hook, if specified. + Runs a command hook as subprocess of current process. + + If cmd is not specified, nothing is executed. + + cmd -- executable file path + args -- list of command line arguments appended to executable call """ if cmd: command = ' '.join(map(str, (cmd,) + args)) From 61083c976bec3063acec5ff2af3a65e9ef6534fb Mon Sep 17 00:00:00 2001 From: And Past Date: Thu, 12 Apr 2018 17:16:51 -0300 Subject: [PATCH 91/93] Create appveyor.yml --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..387207a --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,3 @@ +version: 1.0.{build} +build: + verbosity: minimal From 1913c661bb1360c6b3851416681004dfbefc7173 Mon Sep 17 00:00:00 2001 From: And Past Date: Thu, 12 Apr 2018 17:20:47 -0300 Subject: [PATCH 92/93] Update appveyor.yml --- appveyor.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 387207a..e2acd60 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,4 @@ version: 1.0.{build} -build: - verbosity: minimal + +build_script: + - cmd: python setup.py test From bb39e901f325f8ed4c488b55c366cf80ba76d1a5 Mon Sep 17 00:00:00 2001 From: Andre Pastore Date: Thu, 12 Apr 2018 17:26:03 -0300 Subject: [PATCH 93/93] Added classifier Famework :: Pytest for better pypi index classification --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6d76dea..ed00ab5 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,8 @@ def read(filename): 'console_scripts': [ 'pytest-watch = pytest_watch:main', 'ptw = pytest_watch:main', - ] + ], + #'pytest11': ["watch = pytest_watch:main"] }, extras_require={ 'testing': DEPS_TESTING, @@ -42,4 +43,7 @@ def read(filename): 'dev:python_version in "2.6, 2.7, 3.2"': ['mock'], 'qa:python_version in "2.6, 2.7, 3.2"': ['mock'], }, + classifiers=[ + "Framework :: Pytest", + ] )