Skip to content

Commit 99b0cca

Browse files
committed
Improve tests of fs.wrap wrappers to make sure caching and read-only work
1 parent 694631e commit 99b0cca

File tree

2 files changed

+143
-65
lines changed

2 files changed

+143
-65
lines changed

fs/wrap.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,22 @@ class WrapCachedDir(WrapFS[_F], typing.Generic[_F]):
9292
9393
"""
9494

95+
# FIXME (@althonos): The caching data structure can very likely be
96+
# improved. With the current implementation, if `scandir` result was
97+
# cached for `namespaces=["details", "access"]`, calling `scandir`
98+
# again only with `names=["details"]` will miss the cache, even though
99+
# we are already storing the totality of the required metadata.
100+
#
101+
# A possible solution would be to replaced the cached with a
102+
# Dict[Text, Dict[Text, Dict[Text, Info]]]
103+
# ^ ^ ^ ^~~ the actual info object
104+
# | | |~~ the path of the directory entry
105+
# | |~~ the namespace of the info
106+
# |~~ the cached directory entry
107+
#
108+
# Furthermore, `listdir` and `filterdir` calls should be cached as well,
109+
# since they can be written as wrappers of `scandir`.
110+
95111
wrap_name = "cached-dir"
96112

97113
def __init__(self, wrap_fs): # noqa: D107

tests/test_wrap.py

Lines changed: 127 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,159 @@
11
from __future__ import unicode_literals
22

3+
import operator
34
import unittest
5+
try:
6+
from unittest import mock
7+
except ImportError:
8+
import mock
49

5-
from fs import errors
10+
import six
11+
12+
import fs.wrap
13+
import fs.errors
614
from fs import open_fs
7-
from fs import wrap
15+
from fs.info import Info
816

917

10-
class TestWrap(unittest.TestCase):
11-
def test_readonly(self):
12-
mem_fs = open_fs("mem://")
13-
fs = wrap.read_only(mem_fs)
18+
class TestWrapReadOnly(unittest.TestCase):
1419

15-
with self.assertRaises(errors.ResourceReadOnly):
16-
fs.open("foo", "w")
20+
def setUp(self):
21+
self.fs = open_fs("mem://")
22+
self.ro = fs.wrap.read_only(self.fs)
1723

18-
with self.assertRaises(errors.ResourceReadOnly):
19-
fs.appendtext("foo", "bar")
24+
def tearDown(self):
25+
self.fs.close()
2026

21-
with self.assertRaises(errors.ResourceReadOnly):
22-
fs.appendbytes("foo", b"bar")
27+
def assertReadOnly(self, func, *args, **kwargs):
28+
self.assertRaises(fs.errors.ResourceReadOnly, func, *args, **kwargs)
2329

24-
with self.assertRaises(errors.ResourceReadOnly):
25-
fs.makedir("foo")
30+
def test_open_w(self):
31+
self.assertReadOnly(self.ro.open, "foo", "w")
2632

27-
with self.assertRaises(errors.ResourceReadOnly):
28-
fs.move("foo", "bar")
33+
def test_appendtext(self):
34+
self.assertReadOnly(self.ro.appendtext, "foo", "bar")
2935

30-
with self.assertRaises(errors.ResourceReadOnly):
31-
fs.openbin("foo", "w")
36+
def test_appendbytes(self):
37+
self.assertReadOnly(self.ro.appendbytes, "foo", b"bar")
3238

33-
with self.assertRaises(errors.ResourceReadOnly):
34-
fs.remove("foo")
39+
def test_makedir(self):
40+
self.assertReadOnly(self.ro.makedir, "foo")
3541

36-
with self.assertRaises(errors.ResourceReadOnly):
37-
fs.removedir("foo")
42+
def test_move(self):
43+
self.assertReadOnly(self.ro.move, "foo", "bar")
3844

39-
with self.assertRaises(errors.ResourceReadOnly):
40-
fs.setinfo("foo", {})
45+
def test_openbin_w(self):
46+
self.assertReadOnly(self.ro.openbin, "foo", "w")
4147

42-
with self.assertRaises(errors.ResourceReadOnly):
43-
fs.settimes("foo", {})
48+
def test_remove(self):
49+
self.assertReadOnly(self.ro.remove, "foo")
4450

45-
with self.assertRaises(errors.ResourceReadOnly):
46-
fs.copy("foo", "bar")
51+
def test_removedir(self):
52+
self.assertReadOnly(self.ro.removedir, "foo")
4753

48-
with self.assertRaises(errors.ResourceReadOnly):
49-
fs.create("foo")
54+
def test_removetree(self):
55+
self.assertReadOnly(self.ro.removetree, "foo")
5056

51-
with self.assertRaises(errors.ResourceReadOnly):
52-
fs.writetext("foo", "bar")
57+
def test_setinfo(self):
58+
self.assertReadOnly(self.ro.setinfo, "foo", {})
5359

54-
with self.assertRaises(errors.ResourceReadOnly):
55-
fs.writebytes("foo", b"bar")
60+
def test_settimes(self):
61+
self.assertReadOnly(self.ro.settimes, "foo", {})
5662

57-
with self.assertRaises(errors.ResourceReadOnly):
58-
fs.makedirs("foo/bar")
63+
def test_copy(self):
64+
self.assertReadOnly(self.ro.copy, "foo", "bar")
5965

60-
with self.assertRaises(errors.ResourceReadOnly):
61-
fs.touch("foo")
66+
def test_create(self):
67+
self.assertReadOnly(self.ro.create, "foo")
6268

63-
with self.assertRaises(errors.ResourceReadOnly):
64-
fs.upload("foo", None)
69+
def test_writetext(self):
70+
self.assertReadOnly(self.ro.writetext, "foo", "bar")
6571

66-
with self.assertRaises(errors.ResourceReadOnly):
67-
fs.writefile("foo", None)
72+
def test_writebytes(self):
73+
self.assertReadOnly(self.ro.writebytes, "foo", b"bar")
6874

69-
self.assertTrue(mem_fs.isempty("/"))
70-
mem_fs.writebytes("file", b"read me")
71-
with fs.openbin("file") as read_file:
72-
self.assertEqual(read_file.read(), b"read me")
75+
def test_makedirs(self):
76+
self.assertReadOnly(self.ro.makedirs, "foo/bar")
7377

74-
with fs.open("file", "rb") as read_file:
75-
self.assertEqual(read_file.read(), b"read me")
78+
def test_touch(self):
79+
self.assertReadOnly(self.ro.touch, "foo")
7680

77-
def test_cachedir(self):
78-
mem_fs = open_fs("mem://")
79-
mem_fs.makedirs("foo/bar/baz")
80-
mem_fs.touch("egg")
81+
def test_upload(self):
82+
self.assertReadOnly(self.ro.upload, "foo", six.BytesIO())
8183

82-
fs = wrap.cache_directory(mem_fs)
83-
self.assertEqual(sorted(fs.listdir("/")), ["egg", "foo"])
84-
self.assertEqual(sorted(fs.listdir("/")), ["egg", "foo"])
85-
self.assertTrue(fs.isdir("foo"))
86-
self.assertTrue(fs.isdir("foo"))
87-
self.assertTrue(fs.isfile("egg"))
88-
self.assertTrue(fs.isfile("egg"))
84+
def test_writefile(self):
85+
self.assertReadOnly(self.ro.writefile, "foo", six.StringIO())
8986

90-
self.assertEqual(fs.getinfo("foo"), mem_fs.getinfo("foo"))
91-
self.assertEqual(fs.getinfo("foo"), mem_fs.getinfo("foo"))
87+
def test_openbin_r(self):
88+
self.fs.writebytes("file", b"read me")
89+
with self.ro.openbin("file") as read_file:
90+
self.assertEqual(read_file.read(), b"read me")
91+
92+
def test_open_r(self):
93+
self.fs.writebytes("file", b"read me")
94+
with self.ro.open("file", "rb") as read_file:
95+
self.assertEqual(read_file.read(), b"read me")
9296

93-
self.assertEqual(fs.getinfo("/"), mem_fs.getinfo("/"))
94-
self.assertEqual(fs.getinfo("/"), mem_fs.getinfo("/"))
9597

96-
with self.assertRaises(errors.ResourceNotFound):
97-
fs.getinfo("/foofoo")
98+
class TestWrapCachedDir(unittest.TestCase):
99+
100+
def setUp(self):
101+
self.fs = open_fs("mem://")
102+
self.fs.makedirs("foo/bar/baz")
103+
self.fs.touch("egg")
104+
self.cached = fs.wrap.cache_directory(self.fs)
105+
106+
def tearDown(self):
107+
self.fs.close()
108+
109+
def assertNotFound(self, func, *args, **kwargs):
110+
self.assertRaises(fs.errors.ResourceNotFound, func, *args, **kwargs)
111+
112+
def test_scandir(self):
113+
key = operator.attrgetter("name")
114+
expected = [
115+
Info({"basic": {"name": "egg", "is_dir": False}}),
116+
Info({"basic": {"name": "foo", "is_dir": True}}),
117+
]
118+
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
119+
self.assertEqual(sorted(self.cached.scandir("/"), key=key), expected)
120+
scandir.assert_called()
121+
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
122+
self.assertEqual(sorted(self.cached.scandir("/"), key=key), expected)
123+
scandir.assert_not_called()
124+
125+
def test_isdir(self):
126+
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
127+
self.assertTrue(self.cached.isdir("foo"))
128+
self.assertFalse(self.cached.isdir("egg")) # is file
129+
self.assertFalse(self.cached.isdir("spam")) # doesn't exist
130+
scandir.assert_called()
131+
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
132+
self.assertTrue(self.cached.isdir("foo"))
133+
self.assertFalse(self.cached.isdir("egg"))
134+
self.assertFalse(self.cached.isdir("spam"))
135+
scandir.assert_not_called()
136+
137+
def test_isfile(self):
138+
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
139+
self.assertTrue(self.cached.isfile("egg"))
140+
self.assertFalse(self.cached.isfile("foo")) # is dir
141+
self.assertFalse(self.cached.isfile("spam")) # doesn't exist
142+
scandir.assert_called()
143+
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
144+
self.assertTrue(self.cached.isfile("egg"))
145+
self.assertFalse(self.cached.isfile("foo"))
146+
self.assertFalse(self.cached.isfile("spam"))
147+
scandir.assert_not_called()
148+
149+
def test_getinfo(self):
150+
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
151+
self.assertEqual(self.cached.getinfo("foo"), self.fs.getinfo("foo"))
152+
self.assertEqual(self.cached.getinfo("/"), self.fs.getinfo("/"))
153+
self.assertNotFound(self.cached.getinfo, "spam")
154+
scandir.assert_called()
155+
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
156+
self.assertEqual(self.cached.getinfo("foo"), self.fs.getinfo("foo"))
157+
self.assertEqual(self.cached.getinfo("/"), self.fs.getinfo("/"))
158+
self.assertNotFound(self.cached.getinfo, "spam")
159+
scandir.assert_not_called()

0 commit comments

Comments
 (0)