__init__.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import os
  2. from hashlib import md5
  3. import pytest
  4. from fsspec.implementations.local import LocalFileSystem
  5. from fsspec.tests.abstract.copy import AbstractCopyTests # noqa: F401
  6. from fsspec.tests.abstract.get import AbstractGetTests # noqa: F401
  7. from fsspec.tests.abstract.open import AbstractOpenTests # noqa: F401
  8. from fsspec.tests.abstract.pipe import AbstractPipeTests # noqa: F401
  9. from fsspec.tests.abstract.put import AbstractPutTests # noqa: F401
  10. class BaseAbstractFixtures:
  11. """
  12. Abstract base class containing fixtures that are used by but never need to
  13. be overridden in derived filesystem-specific classes to run the abstract
  14. tests on such filesystems.
  15. """
  16. @pytest.fixture
  17. def fs_bulk_operations_scenario_0(self, fs, fs_join, fs_path):
  18. """
  19. Scenario on remote filesystem that is used for many cp/get/put tests.
  20. Cleans up at the end of each test it which it is used.
  21. """
  22. source = self._bulk_operations_scenario_0(fs, fs_join, fs_path)
  23. yield source
  24. fs.rm(source, recursive=True)
  25. @pytest.fixture
  26. def fs_glob_edge_cases_files(self, fs, fs_join, fs_path):
  27. """
  28. Scenario on remote filesystem that is used for glob edge cases cp/get/put tests.
  29. Cleans up at the end of each test it which it is used.
  30. """
  31. source = self._glob_edge_cases_files(fs, fs_join, fs_path)
  32. yield source
  33. fs.rm(source, recursive=True)
  34. @pytest.fixture
  35. def fs_dir_and_file_with_same_name_prefix(self, fs, fs_join, fs_path):
  36. """
  37. Scenario on remote filesystem that is used to check cp/get/put on directory
  38. and file with the same name prefixes.
  39. Cleans up at the end of each test it which it is used.
  40. """
  41. source = self._dir_and_file_with_same_name_prefix(fs, fs_join, fs_path)
  42. yield source
  43. fs.rm(source, recursive=True)
  44. @pytest.fixture
  45. def fs_10_files_with_hashed_names(self, fs, fs_join, fs_path):
  46. """
  47. Scenario on remote filesystem that is used to check cp/get/put files order
  48. when source and destination are lists.
  49. Cleans up at the end of each test it which it is used.
  50. """
  51. source = self._10_files_with_hashed_names(fs, fs_join, fs_path)
  52. yield source
  53. fs.rm(source, recursive=True)
  54. @pytest.fixture
  55. def fs_target(self, fs, fs_join, fs_path):
  56. """
  57. Return name of remote directory that does not yet exist to copy into.
  58. Cleans up at the end of each test it which it is used.
  59. """
  60. target = fs_join(fs_path, "target")
  61. yield target
  62. if fs.exists(target):
  63. fs.rm(target, recursive=True)
  64. @pytest.fixture
  65. def local_bulk_operations_scenario_0(self, local_fs, local_join, local_path):
  66. """
  67. Scenario on local filesystem that is used for many cp/get/put tests.
  68. Cleans up at the end of each test it which it is used.
  69. """
  70. source = self._bulk_operations_scenario_0(local_fs, local_join, local_path)
  71. yield source
  72. local_fs.rm(source, recursive=True)
  73. @pytest.fixture
  74. def local_glob_edge_cases_files(self, local_fs, local_join, local_path):
  75. """
  76. Scenario on local filesystem that is used for glob edge cases cp/get/put tests.
  77. Cleans up at the end of each test it which it is used.
  78. """
  79. source = self._glob_edge_cases_files(local_fs, local_join, local_path)
  80. yield source
  81. local_fs.rm(source, recursive=True)
  82. @pytest.fixture
  83. def local_dir_and_file_with_same_name_prefix(
  84. self, local_fs, local_join, local_path
  85. ):
  86. """
  87. Scenario on local filesystem that is used to check cp/get/put on directory
  88. and file with the same name prefixes.
  89. Cleans up at the end of each test it which it is used.
  90. """
  91. source = self._dir_and_file_with_same_name_prefix(
  92. local_fs, local_join, local_path
  93. )
  94. yield source
  95. local_fs.rm(source, recursive=True)
  96. @pytest.fixture
  97. def local_10_files_with_hashed_names(self, local_fs, local_join, local_path):
  98. """
  99. Scenario on local filesystem that is used to check cp/get/put files order
  100. when source and destination are lists.
  101. Cleans up at the end of each test it which it is used.
  102. """
  103. source = self._10_files_with_hashed_names(local_fs, local_join, local_path)
  104. yield source
  105. local_fs.rm(source, recursive=True)
  106. @pytest.fixture
  107. def local_target(self, local_fs, local_join, local_path):
  108. """
  109. Return name of local directory that does not yet exist to copy into.
  110. Cleans up at the end of each test it which it is used.
  111. """
  112. target = local_join(local_path, "target")
  113. yield target
  114. if local_fs.exists(target):
  115. local_fs.rm(target, recursive=True)
  116. def _glob_edge_cases_files(self, some_fs, some_join, some_path):
  117. """
  118. Scenario that is used for glob edge cases cp/get/put tests.
  119. Creates the following directory and file structure:
  120. 📁 source
  121. ├── 📄 file1
  122. ├── 📄 file2
  123. ├── 📁 subdir0
  124. │ ├── 📄 subfile1
  125. │ ├── 📄 subfile2
  126. │ └── 📁 nesteddir
  127. │ └── 📄 nestedfile
  128. └── 📁 subdir1
  129. ├── 📄 subfile1
  130. ├── 📄 subfile2
  131. └── 📁 nesteddir
  132. └── 📄 nestedfile
  133. """
  134. source = some_join(some_path, "source")
  135. some_fs.touch(some_join(source, "file1"))
  136. some_fs.touch(some_join(source, "file2"))
  137. for subdir_idx in range(2):
  138. subdir = some_join(source, f"subdir{subdir_idx}")
  139. nesteddir = some_join(subdir, "nesteddir")
  140. some_fs.makedirs(nesteddir)
  141. some_fs.touch(some_join(subdir, "subfile1"))
  142. some_fs.touch(some_join(subdir, "subfile2"))
  143. some_fs.touch(some_join(nesteddir, "nestedfile"))
  144. return source
  145. def _bulk_operations_scenario_0(self, some_fs, some_join, some_path):
  146. """
  147. Scenario that is used for many cp/get/put tests. Creates the following
  148. directory and file structure:
  149. 📁 source
  150. ├── 📄 file1
  151. ├── 📄 file2
  152. └── 📁 subdir
  153. ├── 📄 subfile1
  154. ├── 📄 subfile2
  155. └── 📁 nesteddir
  156. └── 📄 nestedfile
  157. """
  158. source = some_join(some_path, "source")
  159. subdir = some_join(source, "subdir")
  160. nesteddir = some_join(subdir, "nesteddir")
  161. some_fs.makedirs(nesteddir)
  162. some_fs.touch(some_join(source, "file1"))
  163. some_fs.touch(some_join(source, "file2"))
  164. some_fs.touch(some_join(subdir, "subfile1"))
  165. some_fs.touch(some_join(subdir, "subfile2"))
  166. some_fs.touch(some_join(nesteddir, "nestedfile"))
  167. return source
  168. def _dir_and_file_with_same_name_prefix(self, some_fs, some_join, some_path):
  169. """
  170. Scenario that is used to check cp/get/put on directory and file with
  171. the same name prefixes. Creates the following directory and file structure:
  172. 📁 source
  173. ├── 📄 subdir.txt
  174. └── 📁 subdir
  175. └── 📄 subfile.txt
  176. """
  177. source = some_join(some_path, "source")
  178. subdir = some_join(source, "subdir")
  179. file = some_join(source, "subdir.txt")
  180. subfile = some_join(subdir, "subfile.txt")
  181. some_fs.makedirs(subdir)
  182. some_fs.touch(file)
  183. some_fs.touch(subfile)
  184. return source
  185. def _10_files_with_hashed_names(self, some_fs, some_join, some_path):
  186. """
  187. Scenario that is used to check cp/get/put files order when source and
  188. destination are lists. Creates the following directory and file structure:
  189. 📁 source
  190. └── 📄 {hashed([0-9])}.txt
  191. """
  192. source = some_join(some_path, "source")
  193. for i in range(10):
  194. hashed_i = md5(str(i).encode("utf-8")).hexdigest()
  195. path = some_join(source, f"{hashed_i}.txt")
  196. some_fs.pipe(path=path, value=f"{i}".encode())
  197. return source
  198. class AbstractFixtures(BaseAbstractFixtures):
  199. """
  200. Abstract base class containing fixtures that may be overridden in derived
  201. filesystem-specific classes to run the abstract tests on such filesystems.
  202. For any particular filesystem some of these fixtures must be overridden,
  203. such as ``fs`` and ``fs_path``, and others may be overridden if the
  204. default functions here are not appropriate, such as ``fs_join``.
  205. """
  206. @pytest.fixture
  207. def fs(self):
  208. raise NotImplementedError("This function must be overridden in derived classes")
  209. @pytest.fixture
  210. def fs_join(self):
  211. """
  212. Return a function that joins its arguments together into a path.
  213. Most fsspec implementations join paths in a platform-dependent way,
  214. but some will override this to always use a forward slash.
  215. """
  216. return os.path.join
  217. @pytest.fixture
  218. def fs_path(self):
  219. raise NotImplementedError("This function must be overridden in derived classes")
  220. @pytest.fixture(scope="class")
  221. def local_fs(self):
  222. # Maybe need an option for auto_mkdir=False? This is only relevant
  223. # for certain implementations.
  224. return LocalFileSystem(auto_mkdir=True)
  225. @pytest.fixture
  226. def local_join(self):
  227. """
  228. Return a function that joins its arguments together into a path, on
  229. the local filesystem.
  230. """
  231. return os.path.join
  232. @pytest.fixture
  233. def local_path(self, tmpdir):
  234. return tmpdir
  235. @pytest.fixture
  236. def supports_empty_directories(self):
  237. """
  238. Return whether this implementation supports empty directories.
  239. """
  240. return True
  241. @pytest.fixture
  242. def fs_sanitize_path(self):
  243. return lambda x: x