Repository navigation
Increase test coverage for filecmp
#159663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6441227
78aad17
f781d4c
0062674
4644135
4863951
553d1c5
fac6025
55a87b8
2d7d06d
bd65042
c2d6193
4eeb957
e613f5c
ee8905b
f222069
4469ebd
f9f7b06
8f74918
c68621b
c2e2245
54dfe05
2046e40
b3825d8
95b5e2a
5c9f85a
b14bd17
793cc79
3c5f09f
3e6e54f
fffa266
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,12 +1,16 @@ | ||||||||||||||||||||||||||||
| import filecmp | ||||||||||||||||||||||||||||
| import io | ||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||
| import shutil | ||||||||||||||||||||||||||||
| import stat | ||||||||||||||||||||||||||||
| import tempfile | ||||||||||||||||||||||||||||
| import unittest | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||
| from test import support | ||||||||||||||||||||||||||||
| from test.support import os_helper | ||||||||||||||||||||||||||||
| from unittest.mock import patch | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def _create_file_shallow_equal(template_path, new_path): | ||||||||||||||||||||||||||||
|
|
@@ -76,6 +80,25 @@ def test_cache_clear(self): | |||||||||||||||||||||||||||
| self.assertTrue(len(filecmp._cache) == 0, | ||||||||||||||||||||||||||||
| "Cache not cleared after calling clear_cache") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def test_cache_overflow_and_hit(self): | ||||||||||||||||||||||||||||
| filecmp.clear_cache() | ||||||||||||||||||||||||||||
| self.addCleanup(filecmp.clear_cache) | ||||||||||||||||||||||||||||
| s1 = filecmp._sig(os.stat(self.name)) | ||||||||||||||||||||||||||||
| s2 = filecmp._sig(os.stat(self.name_same)) | ||||||||||||||||||||||||||||
| base_dir = os.path.dirname(self.name) | ||||||||||||||||||||||||||||
| for i in range(105): | ||||||||||||||||||||||||||||
| fake_path2 = os.path.join(base_dir, f"fake_path_{i}") | ||||||||||||||||||||||||||||
| filecmp._cache[self.name, fake_path2, s1, s2] = True | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.assertGreater(len(filecmp._cache), 100) | ||||||||||||||||||||||||||||
| res1 = filecmp.cmp(self.name, self.name_same, shallow=False) | ||||||||||||||||||||||||||||
| self.assertTrue(res1) | ||||||||||||||||||||||||||||
| self.assertEqual(len(filecmp._cache), 1) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| res2 = filecmp.cmp(self.name, self.name_same, shallow=False) | ||||||||||||||||||||||||||||
| self.assertTrue(res2) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class DirCompareTestCase(unittest.TestCase): | ||||||||||||||||||||||||||||
| def setUp(self): | ||||||||||||||||||||||||||||
| tmpdir = tempfile.gettempdir() | ||||||||||||||||||||||||||||
|
|
@@ -367,5 +390,164 @@ def _assert_report(self, dircmp_report, expected_report_lines): | |||||||||||||||||||||||||||
| self.assertEqual(report_lines, expected_report_lines) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class DirCmpImplementationTests(unittest.TestCase): | ||||||||||||||||||||||||||||
| def setUp(self): | ||||||||||||||||||||||||||||
| tmpdir = self.enterContext(os_helper.temp_dir()) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.base_test_dir = os.path.join(tmpdir, 'test_filecmp_working_dir') | ||||||||||||||||||||||||||||
| os.makedirs(self.base_test_dir, exist_ok=True) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.dir = os.path.join(self.base_test_dir, 'dir') | ||||||||||||||||||||||||||||
| self.dir_same = os.path.join(self.base_test_dir, 'dir-same') | ||||||||||||||||||||||||||||
| self.dir_diff = os.path.join(self.base_test_dir, 'dir-diff') | ||||||||||||||||||||||||||||
| self.dir_diff_file = os.path.join(self.base_test_dir, 'dir-diff-file') | ||||||||||||||||||||||||||||
| self.dir_same_shallow = os.path.join(self.base_test_dir, 'dir-same-shallow') | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.dir_ignored = os.path.join(self.dir_same, '.hg') | ||||||||||||||||||||||||||||
| self.caseinsensitive = os.path.normcase('A') == os.path.normcase('a') | ||||||||||||||||||||||||||||
| data = 'Contents of file go here.\n' | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Path(self.dir).mkdir(parents=True, exist_ok=True) | ||||||||||||||||||||||||||||
| subdir_path = os.path.join(self.dir, 'subdir') | ||||||||||||||||||||||||||||
| Path(subdir_path).mkdir(parents=True, exist_ok=True) | ||||||||||||||||||||||||||||
| dir_file_path = os.path.join(self.dir, "file") | ||||||||||||||||||||||||||||
| Path(dir_file_path).write_text(data, encoding="utf-8") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| for d in (self.dir_same, self.dir_same_shallow, | ||||||||||||||||||||||||||||
| self.dir_diff, self.dir_diff_file): | ||||||||||||||||||||||||||||
| Path(d, 'subdir').mkdir(parents=True, exist_ok=True) | ||||||||||||||||||||||||||||
| fn = 'FiLe' if (self.caseinsensitive and d == self.dir_same) else 'file' | ||||||||||||||||||||||||||||
| file_path = os.path.join(d, fn) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if d == self.dir_same_shallow: | ||||||||||||||||||||||||||||
| _create_file_shallow_equal(dir_file_path, file_path) | ||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| shutil.copy2(dir_file_path, file_path) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Path(self.dir_diff, 'file2').write_text('An extra file.\n') | ||||||||||||||||||||||||||||
| Path(self.dir_diff_file, 'file2').write_text('Different contents.\n') | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def tearDown(self): | ||||||||||||||||||||||||||||
| filecmp.clear_cache() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Test dircmp behavior when os.stat raises OSError on specific files. | ||||||||||||||||||||||||||||
| # Structure contains 'error_trigger_file.txt' in both directories. | ||||||||||||||||||||||||||||
| def test_dircmp_os_errors_in_processing(self): | ||||||||||||||||||||||||||||
|
Comment on lines
+428
to
+435
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||
| trigger_file = "error_trigger_file.txt" | ||||||||||||||||||||||||||||
| for d in (self.dir, self.dir_same): | ||||||||||||||||||||||||||||
| Path(d, trigger_file).write_text('trigger', encoding='utf-8') | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| expected_path1 = os.path.normcase(os.path.abspath(os.path.join(self.dir, trigger_file))) | ||||||||||||||||||||||||||||
| expected_path2 = os.path.normcase(os.path.abspath(os.path.join(self.dir_same, trigger_file))) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| original_stat = os.stat | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def fake_stat(path, *args, **kwargs): | ||||||||||||||||||||||||||||
| current_path = os.path.normcase(os.path.abspath(os.fspath(path))) | ||||||||||||||||||||||||||||
| if current_path == expected_path1 or current_path == expected_path2: | ||||||||||||||||||||||||||||
| raise OSError | ||||||||||||||||||||||||||||
| return original_stat(path, *args, **kwargs) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| with patch("os.stat", side_effect=fake_stat): | ||||||||||||||||||||||||||||
| dc = filecmp.dircmp(self.dir, self.dir_same) | ||||||||||||||||||||||||||||
| dc.common_funny | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.assertIn(trigger_file, dc.common_funny) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Test common_funny layout with regular files, directories, and sockets. | ||||||||||||||||||||||||||||
| # 'diff_type.txt' is a directory in one path and a regular file in another. | ||||||||||||||||||||||||||||
| # 'funny_type.txt' simulates a socket using stat.S_IFSOCK. | ||||||||||||||||||||||||||||
| def test_dircmp_different_types_and_funny_types(self): | ||||||||||||||||||||||||||||
| file_diff_type = "diff_type.txt" | ||||||||||||||||||||||||||||
| file_funny_type = "funny_type.txt" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| for d in (self.dir, self.dir_same): | ||||||||||||||||||||||||||||
| for f in (file_diff_type, file_funny_type): | ||||||||||||||||||||||||||||
| Path(d, f).write_text("data") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| diff_path1 = os.path.normcase(os.path.abspath(os.path.join(self.dir, file_diff_type))) | ||||||||||||||||||||||||||||
| diff_path2 = os.path.normcase(os.path.abspath(os.path.join(self.dir_same, file_diff_type))) | ||||||||||||||||||||||||||||
| funny_path1 = os.path.normcase(os.path.abspath(os.path.join(self.dir, file_funny_type))) | ||||||||||||||||||||||||||||
| funny_path2 = os.path.normcase(os.path.abspath(os.path.join(self.dir_same, file_funny_type))) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| original_stat = os.stat | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def fake_stat_types(path, *args, **kwargs): | ||||||||||||||||||||||||||||
| st = original_stat(path, *args, **kwargs) | ||||||||||||||||||||||||||||
| current_path = os.path.normcase(os.path.abspath(os.fspath(path))) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if current_path == diff_path1 or current_path == diff_path2: | ||||||||||||||||||||||||||||
| is_dir_same = (current_path == diff_path2) | ||||||||||||||||||||||||||||
| target_type = stat.S_IFREG if is_dir_same else stat.S_IFDIR | ||||||||||||||||||||||||||||
| new_mode = (st.st_mode & ~stat.S_IFREG) | target_type | ||||||||||||||||||||||||||||
| return os.stat_result((new_mode,) + st[1:]) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if current_path == funny_path1 or current_path == funny_path2: | ||||||||||||||||||||||||||||
| new_mode = (st.st_mode & ~stat.S_IFREG) | stat.S_IFSOCK | ||||||||||||||||||||||||||||
| return os.stat_result((new_mode,) + st[1:]) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return st | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| with patch("os.stat", side_effect=fake_stat_types): | ||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually, |
||||||||||||||||||||||||||||
| dc = filecmp.dircmp(self.dir, self.dir_same) | ||||||||||||||||||||||||||||
| dc.common_funny | ||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
No need for accessing
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried moving it out, but the test fails. filecmp.dircmp uses lazy properties, so it doesn't actually call os.stat until we access dc.common_funny. We have to keep this access inside the with patch block, otherwise the mock uninstalls too early. |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.assertIn(file_diff_type, dc.common_funny) | ||||||||||||||||||||||||||||
| self.assertIn(file_funny_type, dc.common_funny) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Test phase4_closure and report() output for common funny files. | ||||||||||||||||||||||||||||
| # Modifies os.stat for 'funny_report_file.txt' to return a socket type, | ||||||||||||||||||||||||||||
| # and patches filecmp._cmp to return an error status (2) for 'funny_file_trigger.txt'. | ||||||||||||||||||||||||||||
| def test_dircmp_closure_and_report_funnies(self): | ||||||||||||||||||||||||||||
| deep_dir_1 = os.path.join(self.dir, "subdir", "deep_folder") | ||||||||||||||||||||||||||||
| deep_dir_2 = os.path.join(self.dir_same, "subdir", "deep_folder") | ||||||||||||||||||||||||||||
| os.mkdir(deep_dir_1) | ||||||||||||||||||||||||||||
| os.mkdir(deep_dir_2) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| funny_trigger = "funny_report_file.txt" | ||||||||||||||||||||||||||||
| funny_file_trigger = "funny_file_trigger.txt" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| for d in (self.dir, self.dir_same): | ||||||||||||||||||||||||||||
| for f in (funny_trigger, funny_file_trigger): | ||||||||||||||||||||||||||||
| Path(d, f).write_text('data', encoding='utf-8') | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| trigger_path1 = os.path.normcase(os.path.abspath(os.path.join(self.dir, funny_trigger))) | ||||||||||||||||||||||||||||
| trigger_path2 = os.path.normcase(os.path.abspath(os.path.join(self.dir_same, funny_trigger))) | ||||||||||||||||||||||||||||
| cmp_path1 = os.path.normcase(os.path.abspath(os.path.join(self.dir, funny_file_trigger))) | ||||||||||||||||||||||||||||
| cmp_path2 = os.path.normcase(os.path.abspath(os.path.join(self.dir_same, funny_file_trigger))) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| original_stat = os.stat | ||||||||||||||||||||||||||||
| original_cmp = filecmp._cmp | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def fake_stat_for_report(path, *args, **kwargs): | ||||||||||||||||||||||||||||
| current_path = os.path.normcase(os.path.abspath(os.fspath(path))) | ||||||||||||||||||||||||||||
| if current_path == trigger_path1 or current_path == trigger_path2: | ||||||||||||||||||||||||||||
| st = original_stat(path, *args, **kwargs) | ||||||||||||||||||||||||||||
| new_mode = (st.st_mode & ~stat.S_IFREG) | stat.S_IFSOCK | ||||||||||||||||||||||||||||
| return os.stat_result((new_mode,) + st[1:]) | ||||||||||||||||||||||||||||
| return original_stat(path, *args, **kwargs) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def fake_private_cmp(a, b, sh): | ||||||||||||||||||||||||||||
| norm_a = os.path.normcase(os.path.abspath(os.fspath(a))) | ||||||||||||||||||||||||||||
| norm_b = os.path.normcase(os.path.abspath(os.fspath(b))) | ||||||||||||||||||||||||||||
| if norm_a in (cmp_path1, cmp_path2) or norm_b in (cmp_path1, cmp_path2): | ||||||||||||||||||||||||||||
| return 2 | ||||||||||||||||||||||||||||
| return original_cmp(a, b, sh) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| with ( | ||||||||||||||||||||||||||||
| patch("os.stat", side_effect=fake_stat_for_report), | ||||||||||||||||||||||||||||
| patch("filecmp._cmp", side_effect=fake_private_cmp), | ||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||
| dc = filecmp.dircmp(self.dir, self.dir_same) | ||||||||||||||||||||||||||||
|
Bolotnyj marked this conversation as resolved.
Resolved
|
||||||||||||||||||||||||||||
| dc.phase4_closure() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| with support.captured_stdout() as mock_stdout: | ||||||||||||||||||||||||||||
| dc.report() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| output = mock_stdout.getvalue() | ||||||||||||||||||||||||||||
| self.assertIn("Common funny cases", output) | ||||||||||||||||||||||||||||
| self.assertIn("Trouble with common files", output) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||||||||
| unittest.main() | ||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.