From 0ca8a936db6ba4c36d93f7ebe4aa210211d67906 Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Thu, 23 Nov 2017 12:26:57 -0800 Subject: [PATCH] improve test coverage for git submodules --- .../configs/git-submodules-invalid-regex.cfg | 9 + tests/test_project.py | 214 ++++++++++++------ wakatime/projects/git.py | 2 +- 3 files changed, 155 insertions(+), 70 deletions(-) create mode 100644 tests/samples/configs/git-submodules-invalid-regex.cfg diff --git a/tests/samples/configs/git-submodules-invalid-regex.cfg b/tests/samples/configs/git-submodules-invalid-regex.cfg new file mode 100644 index 0000000..2291015 --- /dev/null +++ b/tests/samples/configs/git-submodules-invalid-regex.cfg @@ -0,0 +1,9 @@ +[settings] +debug = false +api_key = 1090a6ae-855f-4be7-b8fb-3edbaf1aa3ec +[git] +submodules_disabled = + this/path/does/not/exist + \(invalid regex) + git/asubmodule + that/other/path diff --git a/tests/test_project.py b/tests/test_project.py index 7599df6..79e76f9 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -9,7 +9,6 @@ import logging import os import platform import shutil -import sys import tempfile import time from testfixtures import log_capture @@ -17,11 +16,11 @@ from wakatime.compat import u from wakatime.constants import API_ERROR, SUCCESS from wakatime.exceptions import NotYetImplemented from wakatime.projects.base import BaseProject -from . import utils -from .utils import ANY, CustomResponse, json +from wakatime.projects.git import Git +from .utils import ANY, DynamicIterable, TestCase, CustomResponse, mock, json -class ProjectTestCase(utils.TestCase): +class ProjectTestCase(TestCase): patch_these = [ 'wakatime.packages.requests.adapters.HTTPAdapter.send', 'wakatime.offlinequeue.Queue.push', @@ -170,14 +169,17 @@ class ProjectTestCase(utils.TestCase): entity=os.path.join(tempdir, 'git', 'emptyfile.txt'), ) - def test_ioerror_when_reading_git_branch(self): + @log_capture() + def test_ioerror_when_reading_git_branch(self, logs): + logging.disable(logging.NOTSET) + tempdir = tempfile.mkdtemp() shutil.copytree('tests/samples/projects/git', os.path.join(tempdir, 'git')) shutil.move(os.path.join(tempdir, 'git', 'dot_git'), os.path.join(tempdir, 'git', '.git')) entity = os.path.join(tempdir, 'git', 'emptyfile.txt') - with utils.mock.patch('wakatime.projects.git.open') as mock_open: + with mock.patch('wakatime.projects.git.open') as mock_open: mock_open.side_effect = IOError('') self.shared( @@ -186,6 +188,11 @@ class ProjectTestCase(utils.TestCase): entity=entity, ) + self.assertNothingPrinted() + actual = self.getLogOutput(logs) + expected = 'OSError' if self.isPy35OrNewer else 'IOError' + self.assertIn(expected, actual) + def test_git_detached_head_not_used_as_branch(self): tempdir = tempfile.mkdtemp() shutil.copytree('tests/samples/projects/git-with-detached-head', os.path.join(tempdir, 'git')) @@ -200,16 +207,16 @@ class ProjectTestCase(utils.TestCase): ) def test_svn_project_detected(self): - with utils.mock.patch('wakatime.projects.git.Git.process') as mock_git: + with mock.patch('wakatime.projects.git.Git.process') as mock_git: mock_git.return_value = False - with utils.mock.patch('wakatime.projects.subversion.Subversion._has_xcode_tools') as mock_has_xcode: + with mock.patch('wakatime.projects.subversion.Subversion._has_xcode_tools') as mock_has_xcode: mock_has_xcode.return_value = True - with utils.mock.patch('wakatime.projects.subversion.Popen.communicate') as mock_popen: + with mock.patch('wakatime.projects.subversion.Popen.communicate') as mock_popen: stdout = open('tests/samples/output/svn').read() stderr = '' - mock_popen.return_value = utils.DynamicIterable((stdout, stderr), max_calls=1) + mock_popen.return_value = DynamicIterable((stdout, stderr), max_calls=1) expected = None if platform.system() == 'Windows' else 'svn' self.shared( @@ -217,17 +224,20 @@ class ProjectTestCase(utils.TestCase): entity='projects/svn/afolder/emptyfile.txt', ) - def test_svn_exception_handled(self): - with utils.mock.patch('wakatime.projects.git.Git.process') as mock_git: + @log_capture() + def test_svn_exception_handled(self, logs): + logging.disable(logging.NOTSET) + + with mock.patch('wakatime.projects.git.Git.process') as mock_git: mock_git.return_value = False - with utils.mock.patch('wakatime.projects.subversion.Subversion._has_xcode_tools') as mock_has_xcode: + with mock.patch('wakatime.projects.subversion.Subversion._has_xcode_tools') as mock_has_xcode: mock_has_xcode.return_value = True - with utils.mock.patch('wakatime.projects.subversion.Popen') as mock_popen: + with mock.patch('wakatime.projects.subversion.Popen') as mock_popen: mock_popen.side_effect = OSError('') - with utils.mock.patch('wakatime.projects.subversion.Popen.communicate') as mock_communicate: + with mock.patch('wakatime.projects.subversion.Popen.communicate') as mock_communicate: mock_communicate.side_effect = OSError('') self.shared( @@ -235,17 +245,20 @@ class ProjectTestCase(utils.TestCase): entity='projects/svn/afolder/emptyfile.txt', ) + self.assertNothingPrinted() + self.assertNothingLogged(logs) + def test_svn_on_mac_without_xcode_tools_installed(self): - with utils.mock.patch('wakatime.projects.git.Git.process') as mock_git: + with mock.patch('wakatime.projects.git.Git.process') as mock_git: mock_git.return_value = False - with utils.mock.patch('wakatime.projects.subversion.platform.system') as mock_system: + with mock.patch('wakatime.projects.subversion.platform.system') as mock_system: mock_system.return_value = 'Darwin' - with utils.mock.patch('wakatime.projects.subversion.Popen.communicate') as mock_popen: + with mock.patch('wakatime.projects.subversion.Popen.communicate') as mock_popen: stdout = open('tests/samples/output/svn').read() stderr = '' - mock_popen.return_value = utils.DynamicIterable((stdout, stderr), raise_on_calls=[OSError('')]) + mock_popen.return_value = DynamicIterable((stdout, stderr), raise_on_calls=[OSError('')]) self.shared( expected_project=None, @@ -263,13 +276,13 @@ class ProjectTestCase(utils.TestCase): args = ['--file', entity, '--config', config, '--time', now] - with utils.mock.patch('wakatime.projects.git.Git.process') as mock_git: + with mock.patch('wakatime.projects.git.Git.process') as mock_git: mock_git.return_value = False - with utils.mock.patch('wakatime.projects.subversion.platform.system') as mock_system: + with mock.patch('wakatime.projects.subversion.platform.system') as mock_system: mock_system.return_value = 'Darwin' - with utils.mock.patch('wakatime.projects.subversion.Popen') as mock_popen: + with mock.patch('wakatime.projects.subversion.Popen') as mock_popen: stdout = open('tests/samples/output/svn').read() stderr = '' @@ -297,7 +310,7 @@ class ProjectTestCase(utils.TestCase): response.status_code = 0 self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response - with utils.mock.patch('wakatime.projects.git.Git.process') as mock_git: + with mock.patch('wakatime.projects.git.Git.process') as mock_git: mock_git.return_value = False now = u(int(time.time())) @@ -311,12 +324,15 @@ class ProjectTestCase(utils.TestCase): self.assertEquals('hg', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project']) self.assertEquals('test-hg-branch', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['branch']) - def test_ioerror_when_reading_mercurial_branch(self): + @log_capture() + def test_ioerror_when_reading_mercurial_branch(self, logs): + logging.disable(logging.NOTSET) + response = Response() response.status_code = 0 self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response - with utils.mock.patch('wakatime.projects.git.Git.process') as mock_git: + with mock.patch('wakatime.projects.git.Git.process') as mock_git: mock_git.return_value = False now = u(int(time.time())) @@ -325,13 +341,18 @@ class ProjectTestCase(utils.TestCase): args = ['--file', entity, '--config', config, '--time', now] - with utils.mock.patch('wakatime.projects.mercurial.open') as mock_open: + with mock.patch('wakatime.projects.mercurial.open') as mock_open: mock_open.side_effect = IOError('') execute(args) self.assertEquals('hg', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project']) self.assertEquals('default', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['branch']) + self.assertNothingPrinted() + actual = self.getLogOutput(logs) + expected = 'OSError' if self.isPy35OrNewer else 'IOError' + self.assertIn(expected, actual) + def test_git_submodule_detected(self): tempdir = tempfile.mkdtemp() shutil.copytree('tests/samples/projects/git-with-submodule', os.path.join(tempdir, 'git')) @@ -376,7 +397,7 @@ class ProjectTestCase(utils.TestCase): config='git-submodules-disabled.cfg', ) - def test_git_submodule_detected_but_disabled_using_regex(self): + def test_git_submodule_detected_and_disabled_using_regex(self): tempdir = tempfile.mkdtemp() shutil.copytree('tests/samples/projects/git-with-submodule', os.path.join(tempdir, 'git')) shutil.move(os.path.join(tempdir, 'git', 'dot_git'), os.path.join(tempdir, 'git', '.git')) @@ -391,7 +412,7 @@ class ProjectTestCase(utils.TestCase): config='git-submodules-disabled-using-regex.cfg', ) - def test_git_submodule_detected_but_enabled_using_regex(self): + def test_git_submodule_detected_and_enabled_using_regex(self): tempdir = tempfile.mkdtemp() shutil.copytree('tests/samples/projects/git-with-submodule', os.path.join(tempdir, 'git')) shutil.move(os.path.join(tempdir, 'git', 'dot_git'), os.path.join(tempdir, 'git', '.git')) @@ -406,6 +427,85 @@ class ProjectTestCase(utils.TestCase): config='git-submodules-enabled-using-regex.cfg', ) + @log_capture() + def test_git_submodule_detected_with_invalid_regex(self, logs): + logging.disable(logging.NOTSET) + + tempdir = tempfile.mkdtemp() + shutil.copytree('tests/samples/projects/git-with-submodule', os.path.join(tempdir, 'git')) + shutil.move(os.path.join(tempdir, 'git', 'dot_git'), os.path.join(tempdir, 'git', '.git')) + shutil.move(os.path.join(tempdir, 'git', 'asubmodule', 'dot_git'), os.path.join(tempdir, 'git', 'asubmodule', '.git')) + + entity = os.path.join(tempdir, 'git', 'asubmodule', 'emptyfile.txt') + + self.shared( + expected_project='git', + expected_branch='master', + entity=entity, + config='git-submodules-invalid-regex.cfg', + ) + + self.assertNothingPrinted() + actual = self.getLogOutput(logs) + expected = u('WakaTime WARNING Regex error (unbalanced parenthesis) for disable git submodules pattern: \\(invalid regex)') + if self.isPy35OrNewer: + expected = 'WakaTime WARNING Regex error (unbalanced parenthesis at position 15) for disable git submodules pattern: \\(invalid regex)' + self.assertEquals(expected, actual) + + @log_capture() + def test_git_find_path_from_submodule(self, logs): + logging.disable(logging.NOTSET) + + tempdir = tempfile.mkdtemp() + shutil.copytree('tests/samples/projects/git-with-submodule', os.path.join(tempdir, 'git')) + shutil.move(os.path.join(tempdir, 'git', 'dot_git'), os.path.join(tempdir, 'git', '.git')) + shutil.move(os.path.join(tempdir, 'git', 'asubmodule', 'dot_git'), os.path.join(tempdir, 'git', 'asubmodule', '.git')) + + path = os.path.join(tempdir, 'git', 'asubmodule') + + git = Git(None) + result = git._find_path_from_submodule(path) + + expected = os.path.realpath(os.path.join(tempdir, 'git', '.git', 'modules', 'asubmodule')) + self.assertEquals(expected, result) + self.assertNothingPrinted() + self.assertNothingLogged(logs) + + @log_capture() + def test_git_find_path_from_submodule_handles_exceptions(self, logs): + logging.disable(logging.NOTSET) + + tempdir = tempfile.mkdtemp() + shutil.copytree('tests/samples/projects/git-with-submodule', os.path.join(tempdir, 'git')) + shutil.move(os.path.join(tempdir, 'git', 'dot_git'), os.path.join(tempdir, 'git', '.git')) + shutil.move(os.path.join(tempdir, 'git', 'asubmodule', 'dot_git'), os.path.join(tempdir, 'git', 'asubmodule', '.git')) + + with mock.patch('wakatime.projects.git.open') as mock_open: + mock_open.side_effect = UnicodeDecodeError('utf8', ''.encode('utf8'), 0, 0, '') + + git = Git(None) + path = os.path.join(tempdir, 'git', 'asubmodule') + result = git._find_path_from_submodule(path) + + self.assertIsNone(result) + self.assertNothingPrinted() + actual = self.getLogOutput(logs) + expected = 'UnicodeDecodeError' + self.assertIn(expected, actual) + + with mock.patch('wakatime.projects.git.open') as mock_open: + mock_open.side_effect = IOError('') + + git = Git(None) + path = os.path.join(tempdir, 'git', 'asubmodule') + result = git._find_path_from_submodule(path) + + self.assertIsNone(result) + self.assertNothingPrinted() + actual = self.getLogOutput(logs) + expected = 'OSError' if self.isPy35OrNewer else 'IOError' + self.assertIn(expected, actual) + @log_capture() def test_project_map(self, logs): logging.disable(logging.NOTSET) @@ -424,12 +524,8 @@ class ProjectTestCase(utils.TestCase): self.assertEquals('proj-map', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project']) - self.assertEquals(sys.stdout.getvalue(), '') - self.assertEquals(sys.stderr.getvalue(), '') - - log_output = "\n".join([u(' ').join(x) for x in logs.actual()]) - expected = u('') - self.assertEquals(log_output, expected) + self.assertNothingPrinted() + self.assertNothingLogged(logs) @log_capture() def test_project_map_group_usage(self, logs): @@ -449,37 +545,28 @@ class ProjectTestCase(utils.TestCase): self.assertEquals('proj-map42', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project']) - self.assertEquals(sys.stdout.getvalue(), '') - self.assertEquals(sys.stderr.getvalue(), '') - - log_output = "\n".join([u(' ').join(x) for x in logs.actual()]) - expected = u('') - self.assertEquals(log_output, expected) + self.assertNothingPrinted() + self.assertNothingLogged(logs) @log_capture() def test_project_map_with_invalid_regex(self, logs): logging.disable(logging.NOTSET) - - response = Response() - response.status_code = 0 - self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response + self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = CustomResponse() now = u(int(time.time())) entity = 'tests/samples/projects/project_map42/emptyfile.txt' config = 'tests/samples/configs/project_map_invalid.cfg' args = ['--file', entity, '--config', config, '--time', now] + retval = execute(args) + self.assertEquals(retval, SUCCESS) - execute(args) - - self.assertEquals(sys.stdout.getvalue(), '') - self.assertEquals(sys.stderr.getvalue(), '') - - output = [u(' ').join(x) for x in logs.actual()] + self.assertNothingPrinted() + actual = self.getLogOutput(logs) expected = u('WakaTime WARNING Regex error (unexpected end of regular expression) for projectmap pattern: invalid[({regex') if self.isPy35OrNewer: expected = u('WakaTime WARNING Regex error (unterminated character set at position 7) for projectmap pattern: invalid[({regex') - self.assertEquals(output[0], expected) + self.assertEquals(expected, actual) @log_capture() def test_project_map_with_replacement_group_index_error(self, logs): @@ -494,16 +581,13 @@ class ProjectTestCase(utils.TestCase): config = 'tests/samples/configs/project_map_malformed.cfg' args = ['--file', entity, '--config', config, '--time', now] - retval = execute(args) self.assertEquals(retval, API_ERROR) - self.assertEquals(sys.stdout.getvalue(), '') - self.assertEquals(sys.stderr.getvalue(), '') - - log_output = "\n".join([u(' ').join(x) for x in logs.actual()]) + self.assertNothingPrinted() + actual = self.getLogOutput(logs) expected = u('WakaTime WARNING Regex error (tuple index out of range) for projectmap pattern: proj-map{3}') - self.assertEquals(log_output, expected) + self.assertEquals(expected, actual) @log_capture() def test_project_map_allows_duplicate_keys(self, logs): @@ -523,12 +607,8 @@ class ProjectTestCase(utils.TestCase): self.assertEquals('proj-map-duplicate-5', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project']) - self.assertEquals(sys.stdout.getvalue(), '') - self.assertEquals(sys.stderr.getvalue(), '') - - log_output = "\n".join([u(' ').join(x) for x in logs.actual()]) - expected = u('') - self.assertEquals(log_output, expected) + self.assertNothingPrinted() + self.assertNothingLogged(logs) @log_capture() def test_project_map_allows_colon_in_key(self, logs): @@ -548,9 +628,5 @@ class ProjectTestCase(utils.TestCase): self.assertEquals('proj-map-match', self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['project']) - self.assertEquals(sys.stdout.getvalue(), '') - self.assertEquals(sys.stderr.getvalue(), '') - - log_output = "\n".join([u(' ').join(x) for x in logs.actual()]) - expected = u('') - self.assertEquals(log_output, expected) + self.assertNothingPrinted() + self.assertNothingLogged(logs) diff --git a/wakatime/projects/git.py b/wakatime/projects/git.py index 5b59a61..5435d80 100644 --- a/wakatime/projects/git.py +++ b/wakatime/projects/git.py @@ -123,6 +123,6 @@ class Git(BaseProject): subpath = line[len('gitdir: '):].strip() if os.path.isfile(os.path.join(path, subpath, 'config')) and \ os.path.isfile(os.path.join(path, subpath, 'HEAD')): - return os.path.join(path, subpath) + return os.path.realpath(os.path.join(path, subpath)) return None