test_util_fileio.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. # coding=utf-8
  2. """
  3. Tests for ddns.util.fileio module
  4. """
  5. import os
  6. import shutil
  7. import tempfile
  8. from io import open # Python 2/3 compatible UTF-8 file operations
  9. from __init__ import MagicMock, patch, unittest
  10. import ddns.util.fileio as fileio
  11. # Test constants
  12. TEST_ENCODING_UTF8 = "utf-8"
  13. TEST_ENCODING_ASCII = "ascii"
  14. # Ensure content is unicode f
  15. TEST_CONTENT_MULTILINGUAL = u"Hello World! 测试内容" # fmt: skip
  16. class TestFileIOModule(unittest.TestCase):
  17. """Test fileio module functions"""
  18. def setUp(self):
  19. """Set up test fixtures"""
  20. self.test_content = TEST_CONTENT_MULTILINGUAL
  21. self.test_encoding = TEST_ENCODING_UTF8
  22. def tearDown(self):
  23. """Clean up after tests"""
  24. pass
  25. def test_ensure_directory_exists_success(self):
  26. """Test _ensure_directory_exists creates directory successfully"""
  27. temp_dir = tempfile.mkdtemp()
  28. try:
  29. test_file = os.path.join(temp_dir, "subdir", "test.txt")
  30. fileio._ensure_directory_exists(test_file)
  31. self.assertTrue(os.path.exists(os.path.dirname(test_file)))
  32. finally:
  33. shutil.rmtree(temp_dir, ignore_errors=True)
  34. def test_ensure_directory_exists_already_exists(self):
  35. """Test _ensure_directory_exists when directory already exists"""
  36. temp_dir = tempfile.mkdtemp()
  37. try:
  38. test_file = os.path.join(temp_dir, "test.txt")
  39. # Directory already exists, should not raise error
  40. fileio._ensure_directory_exists(test_file)
  41. self.assertTrue(os.path.exists(temp_dir))
  42. finally:
  43. shutil.rmtree(temp_dir, ignore_errors=True)
  44. def test_ensure_directory_exists_empty_path(self):
  45. """Test _ensure_directory_exists with empty directory path"""
  46. # Should not raise error for relative paths without directory
  47. fileio._ensure_directory_exists("test.txt")
  48. @patch("ddns.util.fileio.os.makedirs")
  49. @patch("ddns.util.fileio.os.path.exists")
  50. @patch("ddns.util.fileio.os.path.dirname")
  51. def test_ensure_directory_exists_makedirs_called(self, mock_dirname, mock_exists, mock_makedirs):
  52. """Test _ensure_directory_exists calls os.makedirs when needed"""
  53. mock_dirname.return_value = "/test/dir"
  54. mock_exists.return_value = False
  55. fileio._ensure_directory_exists("/test/dir/file.txt")
  56. mock_dirname.assert_called_once_with("/test/dir/file.txt")
  57. mock_exists.assert_called_once_with("/test/dir")
  58. mock_makedirs.assert_called_once_with("/test/dir")
  59. @patch("ddns.util.fileio.os.makedirs")
  60. @patch("ddns.util.fileio.os.path.exists")
  61. @patch("ddns.util.fileio.os.path.dirname")
  62. def test_ensure_directory_exists_raises_exception(self, mock_dirname, mock_exists, mock_makedirs):
  63. """Test _ensure_directory_exists properly raises OSError"""
  64. mock_dirname.return_value = "/test/dir"
  65. mock_exists.return_value = False
  66. mock_makedirs.side_effect = OSError("Permission denied")
  67. with self.assertRaises(OSError):
  68. fileio._ensure_directory_exists("/test/dir/file.txt")
  69. def test_read_file_success(self):
  70. """Test read_file with successful file reading"""
  71. # Create temporary file with Python 2/3 compatible approach
  72. temp_fd, temp_path = tempfile.mkstemp()
  73. try:
  74. # Write content using io.open for consistent behavior
  75. with open(temp_path, "w", encoding="utf-8") as temp_file:
  76. temp_file.write(self.test_content)
  77. result = fileio.read_file(temp_path, self.test_encoding)
  78. self.assertEqual(result, self.test_content)
  79. finally:
  80. os.close(temp_fd)
  81. os.unlink(temp_path)
  82. def test_read_file_nonexistent_file(self):
  83. """Test read_file with nonexistent file raises exception"""
  84. with self.assertRaises((OSError, IOError)):
  85. fileio.read_file("nonexistent_file.txt")
  86. def test_read_file_different_encoding(self):
  87. """Test read_file with different encoding"""
  88. # Use ASCII-safe content for encoding test
  89. content = u"ASCII content" # fmt: skip
  90. temp_fd, temp_path = tempfile.mkstemp()
  91. try:
  92. # Write content using io.open for consistent behavior
  93. with open(temp_path, "w", encoding="ascii") as temp_file:
  94. temp_file.write(content)
  95. result = fileio.read_file(temp_path, "ascii")
  96. self.assertEqual(result, content)
  97. finally:
  98. os.close(temp_fd)
  99. os.unlink(temp_path)
  100. @patch("ddns.util.fileio.open")
  101. def test_read_file_with_mock(self, mock_open):
  102. """Test read_file with mocked file operations"""
  103. mock_file = MagicMock()
  104. mock_file.read.return_value = self.test_content
  105. mock_open.return_value.__enter__.return_value = mock_file
  106. result = fileio.read_file("/test/path.txt", self.test_encoding)
  107. self.assertEqual(result, self.test_content)
  108. mock_open.assert_called_once_with("/test/path.txt", "r", encoding=self.test_encoding)
  109. mock_file.read.assert_called_once()
  110. def test_read_file_safely_success(self):
  111. """Test read_file_safely with successful file reading"""
  112. temp_fd, temp_path = tempfile.mkstemp()
  113. try:
  114. # Write content using io.open for consistent behavior
  115. with open(temp_path, "w", encoding="utf-8") as temp_file:
  116. temp_file.write(self.test_content)
  117. result = fileio.read_file_safely(temp_path, self.test_encoding)
  118. self.assertEqual(result, self.test_content)
  119. finally:
  120. os.close(temp_fd)
  121. os.unlink(temp_path)
  122. def test_read_file_safely_nonexistent_file(self):
  123. """Test read_file_safely with nonexistent file returns None"""
  124. result = fileio.read_file_safely("nonexistent_file.txt")
  125. self.assertIsNone(result)
  126. @patch("ddns.util.fileio.read_file")
  127. def test_read_file_safely_exception_handling(self, mock_read_file):
  128. """Test read_file_safely handles exceptions properly"""
  129. test_path = "/test/path.txt"
  130. mock_read_file.side_effect = OSError("File not found")
  131. result = fileio.read_file_safely(test_path)
  132. self.assertIsNone(result)
  133. mock_read_file.assert_called_once_with(test_path, TEST_ENCODING_UTF8)
  134. @patch("ddns.util.fileio.read_file")
  135. def test_read_file_safely_unicode_error(self, mock_read_file):
  136. """Test read_file_safely handles UnicodeDecodeError"""
  137. mock_read_file.side_effect = UnicodeDecodeError("utf-8", b"", 0, 1, "invalid")
  138. result = fileio.read_file_safely("/test/path.txt")
  139. self.assertIsNone(result)
  140. def test_write_file_success(self):
  141. """Test write_file with successful file writing"""
  142. temp_dir = tempfile.mkdtemp()
  143. try:
  144. test_file = os.path.join(temp_dir, "subdir", "test.txt")
  145. fileio.write_file(test_file, self.test_content, self.test_encoding)
  146. # Verify file was created and content is correct
  147. self.assertTrue(os.path.exists(test_file))
  148. with open(test_file, "r", encoding=self.test_encoding) as f:
  149. content = f.read()
  150. self.assertEqual(content, self.test_content)
  151. finally:
  152. shutil.rmtree(temp_dir, ignore_errors=True)
  153. def test_write_file_creates_directory(self):
  154. """Test write_file automatically creates directory"""
  155. temp_dir = tempfile.mkdtemp()
  156. try:
  157. test_file = os.path.join(temp_dir, "new", "nested", "dir", "test.txt")
  158. fileio.write_file(test_file, self.test_content)
  159. # Verify directory structure was created
  160. self.assertTrue(os.path.exists(os.path.dirname(test_file)))
  161. self.assertTrue(os.path.exists(test_file))
  162. finally:
  163. shutil.rmtree(temp_dir, ignore_errors=True)
  164. @patch("ddns.util.fileio._ensure_directory_exists")
  165. @patch("ddns.util.fileio.open")
  166. def test_write_file_with_mock(self, mock_open, mock_ensure_dir):
  167. """Test write_file with mocked operations"""
  168. mock_file = MagicMock()
  169. mock_open.return_value.__enter__.return_value = mock_file
  170. fileio.write_file("/test/path.txt", self.test_content, self.test_encoding)
  171. mock_ensure_dir.assert_called_once_with("/test/path.txt")
  172. mock_open.assert_called_once_with("/test/path.txt", "w", encoding=self.test_encoding)
  173. mock_file.write.assert_called_once_with(self.test_content)
  174. def test_write_file_safely_success(self):
  175. """Test write_file_safely with successful file writing"""
  176. temp_dir = tempfile.mkdtemp()
  177. try:
  178. test_file = os.path.join(temp_dir, "test.txt")
  179. result = fileio.write_file_safely(test_file, self.test_content, self.test_encoding)
  180. self.assertTrue(result)
  181. self.assertTrue(os.path.exists(test_file))
  182. with open(test_file, "r", encoding=self.test_encoding) as f:
  183. content = f.read()
  184. self.assertEqual(content, self.test_content)
  185. finally:
  186. shutil.rmtree(temp_dir, ignore_errors=True)
  187. @patch("ddns.util.fileio.write_file")
  188. def test_write_file_safely_exception_handling(self, mock_write_file):
  189. """Test write_file_safely handles exceptions properly"""
  190. mock_write_file.side_effect = OSError("Permission denied")
  191. result = fileio.write_file_safely("/test/path.txt", self.test_content)
  192. self.assertFalse(result)
  193. mock_write_file.assert_called_once_with("/test/path.txt", self.test_content, "utf-8")
  194. @patch("ddns.util.fileio.write_file")
  195. def test_write_file_safely_unicode_error(self, mock_write_file):
  196. """Test write_file_safely handles UnicodeEncodeError"""
  197. # Create UnicodeEncodeError with proper arguments for Python 2/3 compatibility
  198. try:
  199. # Python 2 - need unicode objects
  200. error = UnicodeEncodeError("utf-8", u"", 0, 1, "invalid") # fmt: skip
  201. except TypeError:
  202. # Python 3 - accepts str objects
  203. error = UnicodeEncodeError("utf-8", "", 0, 1, "invalid")
  204. mock_write_file.side_effect = error
  205. result = fileio.write_file_safely("/test/path.txt", self.test_content)
  206. self.assertFalse(result)
  207. def test_ensure_directory_success(self):
  208. """Test ensure_directory with successful directory creation"""
  209. temp_dir = tempfile.mkdtemp()
  210. try:
  211. test_file = os.path.join(temp_dir, "new", "nested", "test.txt")
  212. result = fileio.ensure_directory(test_file)
  213. self.assertTrue(result)
  214. self.assertTrue(os.path.exists(os.path.dirname(test_file)))
  215. finally:
  216. shutil.rmtree(temp_dir, ignore_errors=True)
  217. def test_ensure_directory_already_exists(self):
  218. """Test ensure_directory when directory already exists"""
  219. temp_dir = tempfile.mkdtemp()
  220. try:
  221. test_file = os.path.join(temp_dir, "test.txt")
  222. result = fileio.ensure_directory(test_file)
  223. self.assertTrue(result)
  224. finally:
  225. shutil.rmtree(temp_dir, ignore_errors=True)
  226. @patch("ddns.util.fileio._ensure_directory_exists")
  227. def test_ensure_directory_exception_handling(self, mock_ensure_dir):
  228. """Test ensure_directory handles exceptions properly"""
  229. mock_ensure_dir.side_effect = OSError("Permission denied")
  230. result = fileio.ensure_directory("/test/path.txt")
  231. self.assertFalse(result)
  232. mock_ensure_dir.assert_called_once_with("/test/path.txt")
  233. @patch("ddns.util.fileio._ensure_directory_exists")
  234. def test_ensure_directory_io_error(self, mock_ensure_dir):
  235. """Test ensure_directory handles IOError"""
  236. mock_ensure_dir.side_effect = IOError("IO Error")
  237. result = fileio.ensure_directory("/test/path.txt")
  238. self.assertFalse(result)
  239. def test_integration_full_workflow(self):
  240. """Integration test for complete file I/O workflow"""
  241. temp_dir = tempfile.mkdtemp()
  242. try:
  243. test_file = os.path.join(temp_dir, "integration", "test.txt")
  244. # Test complete workflow
  245. # 1. Ensure directory
  246. self.assertTrue(fileio.ensure_directory(test_file))
  247. # 2. Write file
  248. fileio.write_file(test_file, self.test_content)
  249. # 3. Read file
  250. content = fileio.read_file(test_file)
  251. self.assertEqual(content, self.test_content)
  252. # 4. Safe operations
  253. updated_content_str = u"Updated content" # fmt: skip
  254. self.assertTrue(fileio.write_file_safely(test_file, updated_content_str))
  255. updated_content = fileio.read_file_safely(test_file)
  256. self.assertEqual(updated_content, updated_content_str)
  257. finally:
  258. shutil.rmtree(temp_dir, ignore_errors=True)
  259. def test_integration_error_scenarios(self):
  260. """Integration test for error handling scenarios"""
  261. # Test nonexistent files
  262. self.assertIsNone(fileio.read_file_safely("/nonexistent/path.txt"))
  263. # Test safe operations return appropriate values
  264. # Use a more portable invalid path that will fail on most systems
  265. try:
  266. # Try to write to root directory (usually fails due to permissions)
  267. invalid_path = "C:\\invalid_root_file.txt" if os.name == "nt" else "/invalid_root_file.txt"
  268. result = fileio.write_file_safely(invalid_path, "content")
  269. # On some systems this might work, on others it might fail
  270. # We just verify it doesn't crash and returns a boolean
  271. self.assertIsInstance(result, bool)
  272. except Exception:
  273. # If any exception occurs, that's also acceptable for this test
  274. # as we're testing that the function handles errors gracefully
  275. pass
  276. def test_utf8_encoding_support(self):
  277. """Test UTF-8 encoding support with various characters"""
  278. # Ensure all test contents are unicode for Python 2 compatibility
  279. try:
  280. # Python 2 - ensure unicode
  281. test_contents = [
  282. u"English text",
  283. u"中文测试",
  284. u"Русский текст",
  285. u"العربية",
  286. u"🌟✨🎉",
  287. u"Mixed: English 中文 🎉",
  288. ] # fmt: skip
  289. except NameError:
  290. # Python 3
  291. test_contents = ["English text", "中文测试", "Русский текст", "العربية", "🌟✨🎉", "Mixed: English 中文 🎉"]
  292. temp_dir = tempfile.mkdtemp()
  293. try:
  294. for i, content in enumerate(test_contents):
  295. test_file = os.path.join(temp_dir, "test_{}.txt".format(i))
  296. # Write and read back
  297. fileio.write_file(test_file, content)
  298. read_content = fileio.read_file(test_file)
  299. self.assertEqual(read_content, content, "Failed for content: {!r}".format(content))
  300. # Test safe operations
  301. self.assertTrue(fileio.write_file_safely(test_file, content))
  302. safe_content = fileio.read_file_safely(test_file)
  303. self.assertEqual(safe_content, content)
  304. finally:
  305. shutil.rmtree(temp_dir, ignore_errors=True)
  306. def test_different_encodings(self):
  307. """Test support for different encodings"""
  308. # Use ASCII-safe content for encoding test
  309. ascii_content = u"ASCII only content" # fmt: skip
  310. temp_dir = tempfile.mkdtemp()
  311. try:
  312. test_file = os.path.join(temp_dir, "ascii_test.txt")
  313. # Write with ASCII encoding
  314. fileio.write_file(test_file, ascii_content, TEST_ENCODING_ASCII)
  315. # Read with ASCII encoding
  316. content = fileio.read_file(test_file, TEST_ENCODING_ASCII)
  317. self.assertEqual(content, ascii_content)
  318. # Test safe operations with encoding
  319. self.assertTrue(fileio.write_file_safely(test_file, ascii_content, TEST_ENCODING_ASCII))
  320. safe_content = fileio.read_file_safely(test_file, TEST_ENCODING_ASCII)
  321. self.assertEqual(safe_content, ascii_content)
  322. finally:
  323. shutil.rmtree(temp_dir, ignore_errors=True)
  324. if __name__ == "__main__":
  325. unittest.main()