diff --git a/pyproject.toml b/pyproject.toml index f4fc33fec..eae5943ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,10 +3,11 @@ requires = ["setuptools"] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] -python_files = 'test_*.py' -testpaths = 'test' # space separated list of paths from root e.g test tests doc/testing -addopts = '--cov=git --cov-report=term --disable-warnings' -filterwarnings = 'ignore::DeprecationWarning' +addopts = "--cov=git --cov-report=term --disable-warnings" +filterwarnings = "ignore::DeprecationWarning" +python_files = "test_*.py" +tmp_path_retention_policy = "failed" +testpaths = "test" # Space separated list of paths from root e.g test tests doc/testing. # --cov coverage # --cov-report term # send report to terminal term-missing -> terminal with line numbers html xml # --cov-report term-missing # to terminal with line numbers @@ -29,7 +30,7 @@ show_error_codes = true implicit_reexport = true # strict = true -# TODO: remove when 'gitdb' is fully annotated +# TODO: Remove when 'gitdb' is fully annotated. exclude = ["^git/ext/gitdb"] [[tool.mypy.overrides]] module = "gitdb.*" @@ -44,5 +45,5 @@ omit = ["*/git/ext/*"] [tool.black] line-length = 120 -target-version = ['py37'] +target-version = ["py37"] extend-exclude = "git/ext/gitdb" diff --git a/test-requirements.txt b/test-requirements.txt index a69181be1..7cfb977a1 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,8 +4,8 @@ ddt >= 1.1.1, != 1.4.3 mock ; python_version < "3.8" mypy pre-commit -pytest +pytest >= 7.3.1 pytest-cov pytest-instafail -pytest-subtests +pytest-mock pytest-sugar diff --git a/test/test_util.py b/test/test_util.py index f75231c98..d345247b1 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -1,11 +1,10 @@ -# test_utils.py +# test_util.py # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors # # This module is part of GitPython and is released under # the BSD License: https://opensource.org/license/bsd-3-clause/ import ast -import contextlib from datetime import datetime import os import pathlib @@ -15,7 +14,7 @@ import sys import tempfile import time -from unittest import SkipTest, mock, skipIf, skipUnless +from unittest import SkipTest, mock import ddt import pytest @@ -44,140 +43,142 @@ from test.lib import TestBase, with_rw_repo -class _Member: - """A member of an IterableList.""" +@pytest.fixture +def permission_error_tmpdir(tmp_path): + """Fixture to test permissions errors in situations where they are not overcome.""" + td = tmp_path / "testdir" + td.mkdir() + (td / "x").write_bytes(b"") - __slots__ = ("name",) + # Set up PermissionError on Windows, where we can't delete read-only files. + (td / "x").chmod(stat.S_IRUSR) - def __init__(self, name): - self.name = name + # Set up PermissionError on Unix, where we can't delete files in read-only directories. + td.chmod(stat.S_IRUSR | stat.S_IXUSR) - def __repr__(self): - return f"{type(self).__name__}({self.name!r})" + yield td -@contextlib.contextmanager -def _tmpdir_to_force_permission_error(): - """Context manager to test permission errors in situations where they are not overcome.""" - if sys.platform == "cygwin": - raise SkipTest("Cygwin can't set the permissions that make the test meaningful.") - if sys.version_info < (3, 8): - raise SkipTest("In 3.7, TemporaryDirectory doesn't clean up after weird permissions.") +class TestRmtree: + """Tests for :func:`git.util.rmtree`.""" - with tempfile.TemporaryDirectory() as parent: - td = pathlib.Path(parent, "testdir") - td.mkdir() - (td / "x").write_bytes(b"") - (td / "x").chmod(stat.S_IRUSR) # Set up PermissionError on Windows. - td.chmod(stat.S_IRUSR | stat.S_IXUSR) # Set up PermissionError on Unix. - yield td + def test_deletes_nested_dir_with_files(self, tmp_path): + td = tmp_path / "testdir" + for d in td, td / "q", td / "s": + d.mkdir() + for f in ( + td / "p", + td / "q" / "w", + td / "q" / "x", + td / "r", + td / "s" / "y", + td / "s" / "z", + ): + f.write_bytes(b"") -@contextlib.contextmanager -def _tmpdir_for_file_not_found(): - """Context manager to test errors deleting a directory that are not due to permissions.""" - with tempfile.TemporaryDirectory() as parent: - yield pathlib.Path(parent, "testdir") # It is deliberately never created. + try: + rmtree(td) + except SkipTest as ex: + pytest.fail(f"rmtree unexpectedly attempts skip: {ex!r}") + assert not td.exists() -@ddt.ddt -class TestUtils(TestBase): - def test_rmtree_deletes_nested_dir_with_files(self): - with tempfile.TemporaryDirectory() as parent: - td = pathlib.Path(parent, "testdir") - for d in td, td / "q", td / "s": - d.mkdir() - for f in ( - td / "p", - td / "q" / "w", - td / "q" / "x", - td / "r", - td / "s" / "y", - td / "s" / "z", - ): - f.write_bytes(b"") + @pytest.mark.skipif( + sys.platform == "cygwin", + reason="Cygwin can't set the permissions that make the test meaningful.", + ) + def test_deletes_dir_with_readonly_files(self, tmp_path): + # Automatically works on Unix, but requires special handling on Windows. + # Not to be confused with what permission_error_tmpdir sets up (see below). - try: - rmtree(td) - except SkipTest as ex: - self.fail(f"rmtree unexpectedly attempts skip: {ex!r}") + td = tmp_path / "testdir" - self.assertFalse(td.exists()) + for d in td, td / "sub": + d.mkdir() + for f in td / "x", td / "sub" / "y": + f.write_bytes(b"") + f.chmod(0) - @skipIf( + try: + rmtree(td) + except SkipTest as ex: + self.fail(f"rmtree unexpectedly attempts skip: {ex!r}") + + assert not td.exists() + + @pytest.mark.skipif( sys.platform == "cygwin", - "Cygwin can't set the permissions that make the test meaningful.", + reason="Cygwin can't set the permissions that make the test meaningful.", ) - def test_rmtree_deletes_dir_with_readonly_files(self): - # Automatically works on Unix, but requires special handling on Windows. - # Not to be confused with what _tmpdir_to_force_permission_error sets up (see below). - with tempfile.TemporaryDirectory() as parent: - td = pathlib.Path(parent, "testdir") - for d in td, td / "sub": - d.mkdir() - for f in td / "x", td / "sub" / "y": - f.write_bytes(b"") - f.chmod(0) + def test_wraps_perm_error_if_enabled(self, mocker, permission_error_tmpdir): + """rmtree wraps PermissionError when HIDE_WINDOWS_KNOWN_ERRORS is true.""" + # Access the module through sys.modules so it is unambiguous which module's + # attribute we patch: the original git.util, not git.index.util even though + # git.index.util "replaces" git.util and is what "import git.util" gives us. + mocker.patch.object(sys.modules["git.util"], "HIDE_WINDOWS_KNOWN_ERRORS", True) + # Disable common chmod functions so the callback can never fix the problem. + mocker.patch.object(os, "chmod") + mocker.patch.object(pathlib.Path, "chmod") + + # Now we can see how an intractable PermissionError is treated. + with pytest.raises(SkipTest): + rmtree(permission_error_tmpdir) + + @pytest.mark.skipif( + sys.platform == "cygwin", + reason="Cygwin can't set the permissions that make the test meaningful.", + ) + def test_does_not_wrap_perm_error_unless_enabled(self, mocker, permission_error_tmpdir): + """rmtree does not wrap PermissionError when HIDE_WINDOWS_KNOWN_ERRORS is false.""" + # See comments in test_wraps_perm_error_if_enabled for details about patching. + mocker.patch.object(sys.modules["git.util"], "HIDE_WINDOWS_KNOWN_ERRORS", False) + mocker.patch.object(os, "chmod") + mocker.patch.object(pathlib.Path, "chmod") + + with pytest.raises(PermissionError): try: - rmtree(td) + rmtree(permission_error_tmpdir) + except SkipTest as ex: + pytest.fail(f"rmtree unexpectedly attempts skip: {ex!r}") + + @pytest.mark.parametrize("hide_windows_known_errors", [False, True]) + def test_does_not_wrap_other_errors(self, tmp_path, mocker, hide_windows_known_errors): + file_not_found_tmpdir = tmp_path / "testdir" # It is deliberately never created. + + # See comments in test_wraps_perm_error_if_enabled for details about patching. + mocker.patch.object(sys.modules["git.util"], "HIDE_WINDOWS_KNOWN_ERRORS", hide_windows_known_errors) + mocker.patch.object(os, "chmod") + mocker.patch.object(pathlib.Path, "chmod") + + with pytest.raises(FileNotFoundError): + try: + rmtree(file_not_found_tmpdir) except SkipTest as ex: self.fail(f"rmtree unexpectedly attempts skip: {ex!r}") - self.assertFalse(td.exists()) - def test_rmtree_can_wrap_exceptions(self): - """rmtree wraps PermissionError when HIDE_WINDOWS_KNOWN_ERRORS is true.""" - with _tmpdir_to_force_permission_error() as td: - # Access the module through sys.modules so it is unambiguous which module's - # attribute we patch: the original git.util, not git.index.util even though - # git.index.util "replaces" git.util and is what "import git.util" gives us. - with mock.patch.object(sys.modules["git.util"], "HIDE_WINDOWS_KNOWN_ERRORS", True): - # Disable common chmod functions so the callback can't fix the problem. - with mock.patch.object(os, "chmod"), mock.patch.object(pathlib.Path, "chmod"): - # Now we can see how an intractable PermissionError is treated. - with self.assertRaises(SkipTest): - rmtree(td) +class TestEnvParsing: + """Tests for environment variable parsing logic in :mod:`git.util`.""" + + @staticmethod + def _run_parse(name, value): + command = [ + sys.executable, + "-c", + f"from git.util import {name}; print(repr({name}))", + ] + output = subprocess.check_output( + command, + env=None if value is None else dict(os.environ, **{name: value}), + text=True, + ) + return ast.literal_eval(output) - @ddt.data( - (False, PermissionError, _tmpdir_to_force_permission_error), - (False, FileNotFoundError, _tmpdir_for_file_not_found), - (True, FileNotFoundError, _tmpdir_for_file_not_found), - ) - def test_rmtree_does_not_wrap_unless_called_for(self, case): - """rmtree doesn't wrap non-PermissionError, nor if HIDE_WINDOWS_KNOWN_ERRORS is false.""" - hide_windows_known_errors, exception_type, tmpdir_context_factory = case - - with tmpdir_context_factory() as td: - # See comments in test_rmtree_can_wrap_exceptions regarding the patching done here. - with mock.patch.object( - sys.modules["git.util"], - "HIDE_WINDOWS_KNOWN_ERRORS", - hide_windows_known_errors, - ): - with mock.patch.object(os, "chmod"), mock.patch.object(pathlib.Path, "chmod"): - with self.assertRaises(exception_type): - try: - rmtree(td) - except SkipTest as ex: - self.fail(f"rmtree unexpectedly attempts skip: {ex!r}") - - @ddt.data("HIDE_WINDOWS_KNOWN_ERRORS", "HIDE_WINDOWS_FREEZE_ERRORS") - def test_env_vars_for_windows_tests(self, name): - def run_parse(value): - command = [ - sys.executable, - "-c", - f"from git.util import {name}; print(repr({name}))", - ] - output = subprocess.check_output( - command, - env=None if value is None else dict(os.environ, **{name: value}), - text=True, - ) - return ast.literal_eval(output) - - for env_var_value, expected_truth_value in ( + @pytest.mark.parametrize( + "env_var_value, expected_truth_value", + [ (None, os.name == "nt"), # True on Windows when the environment variable is unset. ("", False), (" ", False), @@ -193,79 +194,132 @@ def run_parse(value): ("YES", os.name == "nt"), (" no ", False), (" yes ", os.name == "nt"), - ): - with self.subTest(env_var_value=env_var_value): - self.assertIs(run_parse(env_var_value), expected_truth_value) - - _norm_cygpath_pairs = ( - (R"foo\bar", "foo/bar"), - (R"foo/bar", "foo/bar"), - (R"C:\Users", "/cygdrive/c/Users"), - (R"C:\d/e", "/cygdrive/c/d/e"), - ("C:\\", "/cygdrive/c/"), - (R"\\server\C$\Users", "//server/C$/Users"), - (R"\\server\C$", "//server/C$"), - ("\\\\server\\c$\\", "//server/c$/"), - (R"\\server\BAR/", "//server/BAR/"), - (R"D:/Apps", "/cygdrive/d/Apps"), - (R"D:/Apps\fOO", "/cygdrive/d/Apps/fOO"), - (R"D:\Apps/123", "/cygdrive/d/Apps/123"), + ], ) - - _unc_cygpath_pairs = ( - (R"\\?\a:\com", "/cygdrive/a/com"), - (R"\\?\a:/com", "/cygdrive/a/com"), - (R"\\?\UNC\server\D$\Apps", "//server/D$/Apps"), + @pytest.mark.parametrize( + "name", + [ + "HIDE_WINDOWS_KNOWN_ERRORS", + "HIDE_WINDOWS_FREEZE_ERRORS", + ], ) + def test_env_vars_for_windows_tests(self, name, env_var_value, expected_truth_value): + actual_parsed_value = self._run_parse(name, env_var_value) + assert actual_parsed_value is expected_truth_value + + +def _xfail_param(*values, **xfail_kwargs): + """Build a pytest.mark.parametrize parameter that carries an xfail mark.""" + return pytest.param(*values, marks=pytest.mark.xfail(**xfail_kwargs)) + + +_norm_cygpath_pairs = ( + (R"foo\bar", "foo/bar"), + (R"foo/bar", "foo/bar"), + (R"C:\Users", "/cygdrive/c/Users"), + (R"C:\d/e", "/cygdrive/c/d/e"), + ("C:\\", "/cygdrive/c/"), + (R"\\server\C$\Users", "//server/C$/Users"), + (R"\\server\C$", "//server/C$"), + ("\\\\server\\c$\\", "//server/c$/"), + (R"\\server\BAR/", "//server/BAR/"), + (R"D:/Apps", "/cygdrive/d/Apps"), + (R"D:/Apps\fOO", "/cygdrive/d/Apps/fOO"), + (R"D:\Apps/123", "/cygdrive/d/Apps/123"), +) - # FIXME: Mark only the /proc-prefixing cases xfail, somehow (or fix them). - @pytest.mark.xfail( - reason="Many return paths prefixed /proc/cygdrive instead.", - raises=AssertionError, - ) - @skipUnless(sys.platform == "cygwin", "Paths specifically for Cygwin.") - @ddt.idata(_norm_cygpath_pairs + _unc_cygpath_pairs) - def test_cygpath_ok(self, case): - wpath, cpath = case - cwpath = cygpath(wpath) - self.assertEqual(cwpath, cpath, wpath) +_unc_cygpath_pairs = ( + (R"\\?\a:\com", "/cygdrive/a/com"), + (R"\\?\a:/com", "/cygdrive/a/com"), + (R"\\?\UNC\server\D$\Apps", "//server/D$/Apps"), +) - @pytest.mark.xfail( - reason=R'2nd example r".\bar" -> "bar" fails, returns "./bar"', - raises=AssertionError, +# Mapping of expected failures for the test_cygpath_ok test. +_cygpath_ok_xfails = { + # From _norm_cygpath_pairs: + (R"C:\Users", "/cygdrive/c/Users"): "/proc/cygdrive/c/Users", + (R"C:\d/e", "/cygdrive/c/d/e"): "/proc/cygdrive/c/d/e", + ("C:\\", "/cygdrive/c/"): "/proc/cygdrive/c/", + (R"\\server\BAR/", "//server/BAR/"): "//server/BAR", + (R"D:/Apps", "/cygdrive/d/Apps"): "/proc/cygdrive/d/Apps", + (R"D:/Apps\fOO", "/cygdrive/d/Apps/fOO"): "/proc/cygdrive/d/Apps/fOO", + (R"D:\Apps/123", "/cygdrive/d/Apps/123"): "/proc/cygdrive/d/Apps/123", + # From _unc_cygpath_pairs: + (R"\\?\a:\com", "/cygdrive/a/com"): "/proc/cygdrive/a/com", + (R"\\?\a:/com", "/cygdrive/a/com"): "/proc/cygdrive/a/com", +} + + +# Parameter sets for the test_cygpath_ok test. +_cygpath_ok_params = [ + ( + _xfail_param(*case, reason=f"Returns: {_cygpath_ok_xfails[case]!r}", raises=AssertionError) + if case in _cygpath_ok_xfails + else case ) - @skipUnless(sys.platform == "cygwin", "Paths specifically for Cygwin.") - @ddt.data( - (R"./bar", "bar"), - (R".\bar", "bar"), # FIXME: Mark only this one xfail, somehow (or fix it). - (R"../bar", "../bar"), - (R"..\bar", "../bar"), - (R"../bar/.\foo/../chu", "../bar/chu"), + for case in _norm_cygpath_pairs + _unc_cygpath_pairs +] + + +@pytest.mark.skipif(sys.platform != "cygwin", reason="Paths specifically for Cygwin.") +class TestCygpath: + """Tests for :func:`git.util.cygpath` and :func:`git.util.decygpath`.""" + + @pytest.mark.parametrize("wpath, cpath", _cygpath_ok_params) + def test_cygpath_ok(self, wpath, cpath): + cwpath = cygpath(wpath) + assert cwpath == cpath, wpath + + @pytest.mark.parametrize( + "wpath, cpath", + [ + (R"./bar", "bar"), + _xfail_param(R".\bar", "bar", reason="Returns: './bar'", raises=AssertionError), + (R"../bar", "../bar"), + (R"..\bar", "../bar"), + (R"../bar/.\foo/../chu", "../bar/chu"), + ], ) - def test_cygpath_norm_ok(self, case): - wpath, cpath = case + def test_cygpath_norm_ok(self, wpath, cpath): cwpath = cygpath(wpath) - self.assertEqual(cwpath, cpath or wpath, wpath) - - @skipUnless(sys.platform == "cygwin", "Paths specifically for Cygwin.") - @ddt.data( - R"C:", - R"C:Relative", - R"D:Apps\123", - R"D:Apps/123", - R"\\?\a:rel", - R"\\share\a:rel", + assert cwpath == (cpath or wpath), wpath + + @pytest.mark.parametrize( + "wpath", + [ + R"C:", + R"C:Relative", + R"D:Apps\123", + R"D:Apps/123", + R"\\?\a:rel", + R"\\share\a:rel", + ], ) def test_cygpath_invalids(self, wpath): cwpath = cygpath(wpath) - self.assertEqual(cwpath, wpath.replace("\\", "/"), wpath) + assert cwpath == wpath.replace("\\", "/"), wpath - @skipUnless(sys.platform == "cygwin", "Paths specifically for Cygwin.") - @ddt.idata(_norm_cygpath_pairs) - def test_decygpath(self, case): - wpath, cpath = case + @pytest.mark.parametrize("wpath, cpath", _norm_cygpath_pairs) + def test_decygpath(self, wpath, cpath): wcpath = decygpath(cpath) - self.assertEqual(wcpath, wpath.replace("/", "\\"), cpath) + assert wcpath == wpath.replace("/", "\\"), cpath + + +class _Member: + """A member of an IterableList.""" + + __slots__ = ("name",) + + def __init__(self, name): + self.name = name + + def __repr__(self): + return f"{type(self).__name__}({self.name!r})" + + +@ddt.ddt +class TestUtils(TestBase): + """Tests for most utilities in :mod:`git.util`.""" def test_it_should_dashify(self): self.assertEqual("this-is-my-argument", dashify("this_is_my_argument")) @@ -275,14 +329,14 @@ def test_lock_file(self): my_file = tempfile.mktemp() lock_file = LockFile(my_file) assert not lock_file._has_lock() - # release lock we don't have - fine + # Release lock we don't have - fine. lock_file._release_lock() - # get lock + # Get lock. lock_file._obtain_lock_or_raise() assert lock_file._has_lock() - # concurrent access + # Concurrent access. other_lock_file = LockFile(my_file) assert not other_lock_file._has_lock() self.assertRaises(IOError, other_lock_file._obtain_lock_or_raise) @@ -293,7 +347,7 @@ def test_lock_file(self): other_lock_file._obtain_lock_or_raise() self.assertRaises(IOError, lock_file._obtain_lock_or_raise) - # auto-release on destruction + # Auto-release on destruction. del other_lock_file lock_file._obtain_lock_or_raise() lock_file._release_lock() @@ -303,7 +357,7 @@ def test_blocking_lock_file(self): lock_file = BlockingLockFile(my_file) lock_file._obtain_lock() - # next one waits for the lock + # Next one waits for the lock. start = time.time() wait_time = 0.1 wait_lock = BlockingLockFile(my_file, 0.05, wait_time) @@ -318,7 +372,7 @@ def test_user_id(self): self.assertIn("@", get_user_id()) def test_parse_date(self): - # parse_date(from_timestamp()) must return the tuple unchanged + # parse_date(from_timestamp()) must return the tuple unchanged. for timestamp, offset in ( (1522827734, -7200), (1522827734, 0), @@ -326,7 +380,7 @@ def test_parse_date(self): ): self.assertEqual(parse_date(from_timestamp(timestamp, offset)), (timestamp, offset)) - # test all supported formats + # Test all supported formats. def assert_rval(rval, veri_time, offset=0): self.assertEqual(len(rval), 2) self.assertIsInstance(rval[0], int) @@ -334,7 +388,7 @@ def assert_rval(rval, veri_time, offset=0): self.assertEqual(rval[0], veri_time) self.assertEqual(rval[1], offset) - # now that we are here, test our conversion functions as well + # Now that we are here, test our conversion functions as well. utctz = altz_to_utctz_str(offset) self.assertIsInstance(utctz, str) self.assertEqual(utctz_to_altz(verify_utctz(utctz)), offset) @@ -347,13 +401,13 @@ def assert_rval(rval, veri_time, offset=0): iso3 = ("2005.04.07 22:13:11 -0000", 0) alt = ("04/07/2005 22:13:11", 0) alt2 = ("07.04.2005 22:13:11", 0) - veri_time_utc = 1112911991 # the time this represents, in time since epoch, UTC + veri_time_utc = 1112911991 # The time this represents, in time since epoch, UTC. for date, offset in (rfc, iso, iso2, iso3, alt, alt2): assert_rval(parse_date(date), veri_time_utc, offset) # END for each date type - # and failure - self.assertRaises(ValueError, parse_date, datetime.now()) # non-aware datetime + # ...and failure. + self.assertRaises(ValueError, parse_date, datetime.now()) # Non-aware datetime. self.assertRaises(ValueError, parse_date, "invalid format") self.assertRaises(ValueError, parse_date, "123456789 -02000") self.assertRaises(ValueError, parse_date, " 123456789 -0200") @@ -362,7 +416,7 @@ def test_actor(self): for cr in (None, self.rorepo.config_reader()): self.assertIsInstance(Actor.committer(cr), Actor) self.assertIsInstance(Actor.author(cr), Actor) - # END assure config reader is handled + # END ensure config reader is handled @with_rw_repo("HEAD") @mock.patch("getpass.getuser") @@ -402,7 +456,7 @@ def test_actor_get_uid_laziness_called(self, mock_get_uid): mock_get_uid.return_value = "user" committer = Actor.committer(None) author = Actor.author(None) - # We can't test with `self.rorepo.config_reader()` here, as the uuid laziness + # We can't test with `self.rorepo.config_reader()` here, as the UUID laziness # depends on whether the user running the test has their global user.name config set. self.assertEqual(committer.name, "user") self.assertTrue(committer.email.startswith("user@")) @@ -436,30 +490,30 @@ def test_iterable_list(self, case): self.assertEqual(len(ilist), 2) - # contains works with name and identity + # Contains works with name and identity. self.assertIn(name1, ilist) self.assertIn(name2, ilist) self.assertIn(m2, ilist) self.assertIn(m2, ilist) self.assertNotIn("invalid", ilist) - # with string index + # With string index. self.assertIs(ilist[name1], m1) self.assertIs(ilist[name2], m2) - # with int index + # With int index. self.assertIs(ilist[0], m1) self.assertIs(ilist[1], m2) - # with getattr + # With getattr. self.assertIs(ilist.one, m1) self.assertIs(ilist.two, m2) - # test exceptions + # Test exceptions. self.assertRaises(AttributeError, getattr, ilist, "something") self.assertRaises(IndexError, ilist.__getitem__, "something") - # delete by name and index + # Delete by name and index. self.assertRaises(IndexError, ilist.__delitem__, "something") del ilist[name2] self.assertEqual(len(ilist), 1) @@ -494,21 +548,21 @@ def test_altz_to_utctz_str(self): self.assertEqual(altz_to_utctz_str(-59), "+0000") def test_from_timestamp(self): - # Correct offset: UTC+2, should return datetime + tzoffset(+2) + # Correct offset: UTC+2, should return datetime + tzoffset(+2). altz = utctz_to_altz("+0200") self.assertEqual( datetime.fromtimestamp(1522827734, tzoffset(altz)), from_timestamp(1522827734, altz), ) - # Wrong offset: UTC+58, should return datetime + tzoffset(UTC) + # Wrong offset: UTC+58, should return datetime + tzoffset(UTC). altz = utctz_to_altz("+5800") self.assertEqual( datetime.fromtimestamp(1522827734, tzoffset(0)), from_timestamp(1522827734, altz), ) - # Wrong offset: UTC-9000, should return datetime + tzoffset(UTC) + # Wrong offset: UTC-9000, should return datetime + tzoffset(UTC). altz = utctz_to_altz("-9000") self.assertEqual( datetime.fromtimestamp(1522827734, tzoffset(0)), @@ -538,7 +592,7 @@ def test_remove_password_from_command_line(self): redacted_cmd_1 = remove_password_if_present(cmd_1) assert username not in " ".join(redacted_cmd_1) assert password not in " ".join(redacted_cmd_1) - # Check that we use a copy + # Check that we use a copy. assert cmd_1 is not redacted_cmd_1 assert username in " ".join(cmd_1) assert password in " ".join(cmd_1)