Repository: testing-cabal/systemfixtures

Issue:
shutil.copytree to an overlayed dir fails under Python 3.8
When copying a tree to an overlayed dir, I get the following exception casued by `_is_fake_path` not handling DirEntry params:

```python
    shutil.copytree(CHARM_DIR, self.charm_dir)
  File "/usr/lib/python3.8/shutil.py", line 554, in copytree
    return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
  File "/usr/lib/python3.8/shutil.py", line 496, in _copytree
    copy_function(srcobj, dstname)
  File "/usr/lib/python3.8/shutil.py", line 432, in copy2
    copyfile(src, dst, follow_symlinks=follow_symlinks)
  File "/usr/lib/python3.8/shutil.py", line 261, in copyfile
    with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
  File "/home/nessita/canonical/franky/env/lib/python3.8/site-packages/systemfixtures/_overlay.py", line 23, in _new_value
    if self.condition(*args, **kwargs):
  File "/home/nessita/canonical/franky/env/lib/python3.8/site-packages/systemfixtures/filesystem.py", line 146, in _is_fake_path_or_fd
    return self._is_fake_path(path)
  File "/home/nessita/canonical/franky/env/lib/python3.8/site-packages/systemfixtures/filesystem.py", line 133, in _is_fake_path
    if path.startswith(prefix):
AttributeError: 'posix.DirEntry' object has no attribute 'startswith'
```

A possible fix would be something like this:

```python
    129             if isinstance(path, os.DirEntry):
    130                 path = path.name
```

Discussion:
nessita: I will need to propose a slightly different patch because, while the Python documentation for `DirEntry` seems to imply it's available since Python 3.5 (https://docs.python.org/3/library/os.html#os.DirEntry), as explained in this bug https://bugs.python.org/issue28530, the `DirEntry` it's not exposed in the `os` module until 3.6.

The suggested workaround would be something along these lines:

```python

    import os

    try:
        from os import DirEntry
    except ImportError:
        import tempfile
        with tempfile.NamedTemporaryFile() as ftemp:
            scan = os.scandir(os.path.dirname(ftemp.name))
            DirEntry = type(next(scan))
        del scan, ftemp, tempfile
```
jelmer: Python 3.5 is end of life - can you simplify this PR by dropping support for it?

Patch:
diff --git a/systemfixtures/filesystem.py b/systemfixtures/filesystem.py
index 40d9da0..f26a3ee 100644
--- a/systemfixtures/filesystem.py
+++ b/systemfixtures/filesystem.py
@@ -12,6 +12,7 @@ if six.PY2:
     BUILTIN_OPEN = "__builtin__.open"
 if six.PY3:
     BUILTIN_OPEN = "builtins.open"
+    from os import DirEntry
 
 
 GENERIC_APIS = (
@@ -139,6 +140,8 @@ class FakeFilesystem(Fixture):
         def _is_fake_path_or_fd(self, path, *args, **kwargs):
             if isinstance(path, int):
                 path = self._path_from_fd(path)
+            elif isinstance(path, DirEntry):
+                path = path.name
             return self._is_fake_path(path)
 
     def _is_fake_symlink(self, src, dst, *args, **kwargs):


Test patch:
diff --git a/systemfixtures/tests/test_filesystem.py b/systemfixtures/tests/test_filesystem.py
index 5041bb0..ec3d26a 100644
--- a/systemfixtures/tests/test_filesystem.py
+++ b/systemfixtures/tests/test_filesystem.py
@@ -97,6 +97,12 @@ class FakeFilesystemTest(TestCase):
         shutil.rmtree("/foo/bar")
         self.assertEqual([], os.listdir("/foo"))
 
+    def test_copytree(self):
+        self.fs.add("/foo")
+        shutil.copytree("./doc", "/foo")
+        self.assertEqual(
+            sorted(os.listdir("./doc")), sorted(os.listdir("/foo")))
+
     if six.PY3:
 
         def test_listdir_with_fd(self):