conftest.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import os
  2. import shutil
  3. import subprocess
  4. import sys
  5. import time
  6. from collections import deque
  7. from collections.abc import Generator, Sequence
  8. import pytest
  9. import fsspec
  10. @pytest.fixture()
  11. def m():
  12. """
  13. Fixture providing a memory filesystem.
  14. """
  15. m = fsspec.filesystem("memory")
  16. m.store.clear()
  17. m.pseudo_dirs.clear()
  18. m.pseudo_dirs.append("")
  19. try:
  20. yield m
  21. finally:
  22. m.store.clear()
  23. m.pseudo_dirs.clear()
  24. m.pseudo_dirs.append("")
  25. class InstanceCacheInspector:
  26. """
  27. Helper class to inspect instance caches of filesystem classes in tests.
  28. """
  29. def clear(self) -> None:
  30. """
  31. Clear instance caches of all currently imported filesystem classes.
  32. """
  33. classes = deque([fsspec.spec.AbstractFileSystem])
  34. while classes:
  35. cls = classes.popleft()
  36. cls.clear_instance_cache()
  37. classes.extend(cls.__subclasses__())
  38. def gather_counts(self, *, omit_zero: bool = True) -> dict[str, int]:
  39. """
  40. Gather counts of filesystem instances in the instance caches
  41. of all currently imported filesystem classes.
  42. Parameters
  43. ----------
  44. omit_zero:
  45. Whether to omit instance types with no cached instances.
  46. """
  47. out: dict[str, int] = {}
  48. classes = deque([fsspec.spec.AbstractFileSystem])
  49. while classes:
  50. cls = classes.popleft()
  51. count = len(cls._cache) # there is no public interface for the cache
  52. # note: skip intermediate AbstractFileSystem subclasses
  53. # if they proxy the protocol attribute via a property.
  54. if isinstance(cls.protocol, (Sequence, str)):
  55. key = cls.protocol if isinstance(cls.protocol, str) else cls.protocol[0]
  56. if count or not omit_zero:
  57. out[key] = count
  58. classes.extend(cls.__subclasses__())
  59. return out
  60. @pytest.fixture(scope="function", autouse=True)
  61. def instance_caches() -> Generator[InstanceCacheInspector, None, None]:
  62. """
  63. Fixture to ensure empty filesystem instance caches before and after a test.
  64. Used by default for all tests.
  65. Clears caches of all imported filesystem classes.
  66. Can be used to write test assertions about instance caches.
  67. Usage:
  68. def test_something(instance_caches):
  69. # Test code here
  70. fsspec.open("file://abc")
  71. fsspec.open("memory://foo/bar")
  72. # Test assertion
  73. assert instance_caches.gather_counts() == {"file": 1, "memory": 1}
  74. Returns
  75. -------
  76. instance_caches: An instance cache inspector for clearing and inspecting caches.
  77. """
  78. ic = InstanceCacheInspector()
  79. ic.clear()
  80. try:
  81. yield ic
  82. finally:
  83. ic.clear()
  84. @pytest.fixture(scope="function")
  85. def ftp_writable(tmpdir):
  86. """
  87. Fixture providing a writable FTP filesystem.
  88. """
  89. pytest.importorskip("pyftpdlib")
  90. d = str(tmpdir)
  91. with open(os.path.join(d, "out"), "wb") as f:
  92. f.write(b"hello" * 10000)
  93. P = subprocess.Popen(
  94. [sys.executable, "-m", "pyftpdlib", "-d", d, "-u", "user", "-P", "pass", "-w"]
  95. )
  96. try:
  97. time.sleep(1)
  98. yield "localhost", 2121, "user", "pass"
  99. finally:
  100. P.terminate()
  101. P.wait()
  102. try:
  103. shutil.rmtree(tmpdir)
  104. except Exception:
  105. pass