1#! /usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4# test_virtual_path.py --- Test module for terrasync.virtual_path
5# Copyright (C) 2018  Florent Rougon
6#
7# This program is free software; you can redistribute it and/or
8# modify it under the terms of the GNU General Public License as
9# published by the Free Software Foundation; either version 2 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15# General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21# In order to exercise all tests, run the following command from the parent
22# directory (you may omit the 'discover' argument):
23#
24#   python3 -m unittest discover
25
26import collections
27import unittest
28
29from terrasync.virtual_path import VirtualPath, MutableVirtualPath
30
31# Hook doctest-based tests into the unittest test discovery mechanism
32import doctest
33import terrasync.virtual_path
34
35def load_tests(loader, tests, ignore):
36    # Tell unittest to run doctests from terrasync.virtual_path
37    tests.addTests(doctest.DocTestSuite(terrasync.virtual_path))
38    return tests
39
40
41class VirtualPathCommonTests:
42    """Common tests to run for both VirtualPath and MutableVirtualPath.
43
44    The tests inside this class must exercice the class (VirtualPath or
45    MutableVirtualPath) stored in the 'cls' class attribute. They must
46    work for both VirtualPath and MutableVirtualPath, otherwise they
47    don't belong here!
48
49    """
50
51    def test_normalizeStringPath(self):
52        self.assertEqual(self.cls.normalizeStringPath("/"), "/")
53        self.assertEqual(self.cls.normalizeStringPath(""), "/")
54        self.assertEqual(
55            self.cls.normalizeStringPath("/abc/Def ijk//l Mn///op/q/rst/"),
56            "/abc/Def ijk/l Mn/op/q/rst")
57        self.assertEqual(self.cls.normalizeStringPath("abc/def"), "/abc/def")
58        self.assertEqual(self.cls.normalizeStringPath("/abc/def"), "/abc/def")
59        self.assertEqual(self.cls.normalizeStringPath("//abc/def"),
60                         "/abc/def")
61        self.assertEqual(self.cls.normalizeStringPath("///abc/def"),
62                         "/abc/def")
63        self.assertEqual(self.cls.normalizeStringPath("/abc//def"),
64                         "/abc/def")
65
66    # Unless the implementation of VirtualPath.__init__() has changed
67    # meanwhile, the following function must be essentially the same as
68    # test_normalizeStringPath().
69    def test_constructor_and_str(self):
70        p = self.cls("/")
71        self.assertEqual(str(p), "/")
72
73        p = self.cls("")
74        self.assertEqual(str(p), "/")
75
76        p = self.cls("/abc/Def ijk//l Mn///op/q/rst/")
77        self.assertEqual(str(p), "/abc/Def ijk/l Mn/op/q/rst")
78
79        p = self.cls("abc/def")
80        self.assertEqual(str(p), "/abc/def")
81
82        p = self.cls("/abc/def")
83        self.assertEqual(str(p), "/abc/def")
84
85        p = self.cls("//abc/def")
86        self.assertEqual(str(p), "/abc/def")
87
88        p = self.cls("///abc/def")
89        self.assertEqual(str(p), "/abc/def")
90
91        p = self.cls("/abc//def")
92        self.assertEqual(str(p), "/abc/def")
93
94    def test_asPosix (self):
95        self.assertEqual(self.cls("").asPosix(), "/")
96        self.assertEqual(self.cls("/").asPosix(), "/")
97        self.assertEqual(self.cls("/abc//def").asPosix(), "/abc/def")
98        self.assertEqual(self.cls("/abc//def/").asPosix(), "/abc/def")
99        self.assertEqual(self.cls("//abc//def//").asPosix(), "/abc/def")
100        self.assertEqual(self.cls("////abc//def//").asPosix(), "/abc/def")
101
102    def test_samePath(self):
103        self.assertTrue(self.cls("").samePath(self.cls("")))
104        self.assertTrue(self.cls("").samePath(self.cls("/")))
105        self.assertTrue(self.cls("/").samePath(self.cls("")))
106        self.assertTrue(self.cls("/").samePath(self.cls("/")))
107
108        self.assertTrue(
109            self.cls("/abc/def").samePath(self.cls("/abc/def")))
110        self.assertTrue(
111            self.cls("/abc//def").samePath(self.cls("/abc/def")))
112        self.assertTrue(
113            self.cls("/abc/def/").samePath(self.cls("/abc/def")))
114
115    def test_comparisons(self):
116        self.assertEqual(self.cls("/abc/def"), self.cls("/abc/def"))
117        self.assertEqual(self.cls("/abc//def"), self.cls("/abc/def"))
118        self.assertEqual(self.cls("/abc/def/"), self.cls("/abc/def"))
119
120        self.assertNotEqual(self.cls("/abc/dEf"), self.cls("/abc/def"))
121        self.assertNotEqual(self.cls("/abc/def "), self.cls("/abc/def"))
122
123        self.assertLessEqual(self.cls("/foo/bar"), self.cls("/foo/bar"))
124        self.assertLessEqual(self.cls("/foo/bar"), self.cls("/foo/bbr"))
125        self.assertLess(self.cls("/foo/bar"), self.cls("/foo/bbr"))
126
127        self.assertGreaterEqual(self.cls("/foo/bar"), self.cls("/foo/bar"))
128        self.assertGreaterEqual(self.cls("/foo/bbr"), self.cls("/foo/bar"))
129        self.assertGreater(self.cls("/foo/bbr"), self.cls("/foo/bar"))
130
131    def test_truedivOperators(self):
132        """
133        Test operators used to add paths components to a VirtualPath instance."""
134        p = self.cls("/foo/bar/baz/quux/zoot")
135        self.assertEqual(p, self.cls("/") / "foo" / "bar" / "baz/quux/zoot")
136        self.assertEqual(p, self.cls("/foo") / "bar" / "baz/quux/zoot")
137        self.assertEqual(p, self.cls("/foo/bar") / "baz/quux/zoot")
138
139    def test_joinpath(self):
140        p = self.cls("/foo/bar/baz/quux/zoot")
141        self.assertEqual(
142            p,
143            self.cls("/foo").joinpath("bar", "baz", "quux/zoot"))
144
145    def test_nameAttribute(self):
146        self.assertEqual(self.cls("/").name, "")
147
148        p = self.cls("/foo/bar/baz/quux/zoot")
149        self.assertEqual(p.name, "zoot")
150
151    def test_partsAttribute(self):
152        self.assertEqual(self.cls("/").parts, ("/",))
153
154        p = self.cls("/foo/bar/baz/quux/zoot")
155        self.assertEqual(p.parts, ("/", "foo", "bar", "baz", "quux", "zoot"))
156
157    def test_parentsAttribute(self):
158        def pathify(*args):
159            return tuple( (self.cls(s) for s in args) )
160
161        p = self.cls("/")
162        self.assertEqual(tuple(p.parents), pathify()) # empty tuple
163
164        p = self.cls("/foo")
165        self.assertEqual(tuple(p.parents), pathify("/"))
166
167        p = self.cls("/foo/bar")
168        self.assertEqual(tuple(p.parents), pathify("/foo", "/"))
169
170        p = self.cls("/foo/bar/baz")
171        self.assertEqual(tuple(p.parents), pathify("/foo/bar", "/foo", "/"))
172
173    def test_parentAttribute(self):
174        def pathify(s):
175            return self.cls(s)
176
177        p = self.cls("/")
178        self.assertEqual(p.parent, pathify("/"))
179
180        p = self.cls("/foo")
181        self.assertEqual(p.parent, pathify("/"))
182
183        p = self.cls("/foo/bar")
184        self.assertEqual(p.parent, pathify("/foo"))
185
186        p = self.cls("/foo/bar/baz")
187        self.assertEqual(p.parent, pathify("/foo/bar"))
188
189    def test_suffixAttribute(self):
190        p = self.cls("/")
191        self.assertEqual(p.suffix, '')
192
193        p = self.cls("/foo/bar/baz.py")
194        self.assertEqual(p.suffix, '.py')
195
196        p = self.cls("/foo/bar/baz.py.bla")
197        self.assertEqual(p.suffix, '.bla')
198
199        p = self.cls("/foo/bar/baz")
200        self.assertEqual(p.suffix, '')
201
202    def test_suffixesAttribute(self):
203        p = self.cls("/")
204        self.assertEqual(p.suffixes, [])
205
206        p = self.cls("/foo/bar/baz.py")
207        self.assertEqual(p.suffixes, ['.py'])
208
209        p = self.cls("/foo/bar/baz.py.bla")
210        self.assertEqual(p.suffixes, ['.py', '.bla'])
211
212        p = self.cls("/foo/bar/baz")
213        self.assertEqual(p.suffixes, [])
214
215    def test_stemAttribute(self):
216        p = self.cls("/")
217        self.assertEqual(p.stem, '')
218
219        p = self.cls("/foo/bar/baz.py")
220        self.assertEqual(p.stem, 'baz')
221
222        p = self.cls("/foo/bar/baz.py.bla")
223        self.assertEqual(p.stem, 'baz.py')
224
225    def test_asRelative(self):
226        self.assertEqual(self.cls("/").asRelative(), "")
227        self.assertEqual(self.cls("/foo/bar/baz/quux/zoot").asRelative(),
228                         "foo/bar/baz/quux/zoot")
229
230    def test_relativeTo(self):
231        self.assertEqual(self.cls("").relativeTo(""), "")
232        self.assertEqual(self.cls("").relativeTo("/"), "")
233        self.assertEqual(self.cls("/").relativeTo("/"), "")
234        self.assertEqual(self.cls("/").relativeTo(""), "")
235
236        p = self.cls("/foo/bar/baz/quux/zoot")
237
238        self.assertEqual(p.relativeTo(""), "foo/bar/baz/quux/zoot")
239        self.assertEqual(p.relativeTo("/"), "foo/bar/baz/quux/zoot")
240
241        self.assertEqual(p.relativeTo("foo"), "bar/baz/quux/zoot")
242        self.assertEqual(p.relativeTo("foo/"), "bar/baz/quux/zoot")
243        self.assertEqual(p.relativeTo("/foo"), "bar/baz/quux/zoot")
244        self.assertEqual(p.relativeTo("/foo/"), "bar/baz/quux/zoot")
245
246        self.assertEqual(p.relativeTo("foo/bar/baz"), "quux/zoot")
247        self.assertEqual(p.relativeTo("foo/bar/baz/"), "quux/zoot")
248        self.assertEqual(p.relativeTo("/foo/bar/baz"), "quux/zoot")
249        self.assertEqual(p.relativeTo("/foo/bar/baz/"), "quux/zoot")
250
251        with self.assertRaises(ValueError):
252            p.relativeTo("/foo/ba")
253
254        with self.assertRaises(ValueError):
255            p.relativeTo("/foo/balloon")
256
257    def test_withName(self):
258        p = self.cls("/foo/bar/baz/quux/zoot")
259
260        self.assertEqual(p.withName(""),
261                         VirtualPath("/foo/bar/baz/quux"))
262        self.assertEqual(p.withName("pouet"),
263                         VirtualPath("/foo/bar/baz/quux/pouet"))
264        self.assertEqual(p.withName("pouet/zdong"),
265                         VirtualPath("/foo/bar/baz/quux/pouet/zdong"))
266
267        # The self.cls object has no 'name' (referring to the 'name' property)
268        with self.assertRaises(ValueError):
269            self.cls("").withName("foobar")
270
271        with self.assertRaises(ValueError):
272            self.cls("/").withName("foobar")
273
274    def test_withSuffix(self):
275        p = self.cls("/foo/bar/baz.tar.gz")
276        self.assertEqual(p.withSuffix(".bz2"),
277                         VirtualPath("/foo/bar/baz.tar.bz2"))
278        p = self.cls("/foo/bar/baz")
279        self.assertEqual(p.withSuffix(".tar.xz"),
280                         VirtualPath("/foo/bar/baz.tar.xz"))
281
282        # The self.cls object has no 'name' (referring to the 'name' property)
283        with self.assertRaises(ValueError):
284            self.cls("/foo/bar/baz.tar.gz").withSuffix("no-leading-dot")
285
286        with self.assertRaises(ValueError):
287            # The root virtual path ('/') can't be used for this
288            self.cls("/").withSuffix(".foobar")
289
290
291class TestVirtualPath(unittest.TestCase, VirtualPathCommonTests):
292    """Tests for the VirtualPath class.
293
294    These are the tests using the common infrastructure from
295    VirtualPathCommonTests.
296
297    """
298
299    cls = VirtualPath
300
301class TestVirtualPathSpecific(unittest.TestCase):
302    """Tests specific to the VirtualPath class."""
303
304    def test_isHashableType(self):
305        p = VirtualPath("/foo")
306        self.assertTrue(isinstance(p, collections.Hashable))
307
308    def test_insideSet(self):
309        l1 = [ VirtualPath("/foo/bar"),
310               VirtualPath("/foo/baz") ]
311        l2 = l1 + [ VirtualPath("/foo/bar") ] # l2 has a duplicate element
312
313        # Sets allow one to ignore duplicate elements when comparing
314        self.assertEqual(set(l1), set(l2))
315        self.assertEqual(frozenset(l1), frozenset(l2))
316
317
318class TestMutableVirtualPath(unittest.TestCase, VirtualPathCommonTests):
319    """Tests for the MutableVirtualPath class.
320
321    These are the tests using the common infrastructure from
322    VirtualPathCommonTests.
323
324    """
325
326    cls = MutableVirtualPath
327
328class TestMutableVirtualPathSpecific(unittest.TestCase):
329    """Tests specific to the MutableVirtualPath class."""
330
331    def test_mixedComparisons(self):
332        self.assertTrue(
333            VirtualPath("/abc/def").samePath(MutableVirtualPath("/abc/def")))
334        self.assertTrue(
335            VirtualPath("/abc//def").samePath(MutableVirtualPath("/abc/def")))
336        self.assertTrue(
337            VirtualPath("/abc/def/").samePath(MutableVirtualPath("/abc/def")))
338
339        self.assertTrue(
340            MutableVirtualPath("/abc/def").samePath(VirtualPath("/abc/def")))
341        self.assertTrue(
342            MutableVirtualPath("/abc//def").samePath(VirtualPath("/abc/def")))
343        self.assertTrue(
344            MutableVirtualPath("/abc/def/").samePath(VirtualPath("/abc/def")))
345
346    def test_inPlacePathConcatenation(self):
347        p = VirtualPath("/foo/bar/baz/quux/zoot")
348
349        q = MutableVirtualPath("/foo")
350        q /= "bar"
351        q /= "baz/quux/zoot"
352
353        self.assertTrue(p.samePath(q))
354
355    def test_isNotHashableType(self):
356        p = MutableVirtualPath("/foo")
357        self.assertFalse(isinstance(p, collections.Hashable))
358
359
360if __name__ == "__main__":
361    unittest.main()
362