c0e1f29a75
Add classes VirtualPath and MutableVirtualPath (the latter derived from the former) to manipulate slash-separated paths where the root '/' represents the TerraScenery root. This makes it clear what a function expects when you see that one of its arguments is a VirtualPath instance: you don't have to ask yourself whether it can start or end with a slash, how to interpret it, etc. Operating on these paths is also easy[1], be it to assemble URLs in order to retrieve files or to join their relative part with a local directory path in order to obtain a real (deeper) local path. VirtualPath and MutableVirtualPath are essentially the same; the former is hashable and therefore has to be immutable, whereas the latter can be modified in-place with the /= operator (used to append path components), and therefore can't be hashable. As a consequence, MutableVirtualPath instances can't be used as dictionary keys, elements of a set or frozenset, etc. VirtualPath and MutableVirtualPath use the pathlib.PurePath API where applicable (part of this API has been implemented in [Mutable]VirtualPath; more can be added, of course). These classes have no assumptions related to TerraSync and thus should be fit for use in other projects. To convert a [Mutable]VirtualPath instance to a string, just use str() on it. The result is guaranteed to start with a '/' and not to end with a '/', except for the virtual root '/'. Upon construction, the given string is interpreted relatively to the virtual root, i.e.: VirtualPath("") == VirtualPath("/") VirtualPath("abc/def/ghi") == VirtualPath("/abc/def/ghi") etc. VirtualPath and MutableVirtualPath instances sort like the respective strings str() converts them too. The __hash__() method of VirtualPath is based on the type and this string representation, too. Such objects can only compare equal (using ==) if they have the same type. If you want to compare the underlying virtual paths inside a VirtualPath and a MutableVirtualPath, use the samePath() method of either class. For more info, see scripts/python/TerraSync/terrasync/virtual_path.py and unit tests in scripts/python/TerraSync/tests/test_virtual_path.py. [1] Most useful is the / operator, which works as for SGPath: VirtualPath("/abc/def/ghi") == VirtualPath("/abc") / "def" / "ghi" VirtualPath("/abc/def/ghi") == VirtualPath("/abc") / "def/ghi"
286 lines
9.7 KiB
Python
286 lines
9.7 KiB
Python
#! /usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# test_virtual_path.py --- Test module for terrasync.virtual_path
|
|
# Copyright (C) 2018 Florent Rougon
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as
|
|
# published by the Free Software Foundation; either version 2 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
# In order to exercise all tests, run the following command from the parent
|
|
# directory (you may omit the 'discover' argument):
|
|
#
|
|
# python3 -m unittest discover
|
|
|
|
import collections
|
|
import unittest
|
|
|
|
from terrasync.virtual_path import VirtualPath, MutableVirtualPath
|
|
|
|
# Hook doctest-based tests into the unittest test discovery mechanism
|
|
import doctest
|
|
import terrasync.virtual_path
|
|
|
|
def load_tests(loader, tests, ignore):
|
|
# Tell unittest to run doctests from terrasync.virtual_path
|
|
tests.addTests(doctest.DocTestSuite(terrasync.virtual_path))
|
|
return tests
|
|
|
|
|
|
class VirtualPathCommonTests:
|
|
"""Common tests to run for both VirtualPath and MutableVirtualPath.
|
|
|
|
The tests inside this class must exercice the class (VirtualPath or
|
|
MutableVirtualPath) stored in the 'cls' class attribute. They must
|
|
work for both VirtualPath and MutableVirtualPath, otherwise they
|
|
don't belong here!
|
|
|
|
"""
|
|
|
|
def test_normalizeStringPath(self):
|
|
self.assertEqual(self.cls.normalizeStringPath("/"), "/")
|
|
self.assertEqual(self.cls.normalizeStringPath(""), "/")
|
|
self.assertEqual(
|
|
self.cls.normalizeStringPath("/abc/Def ijk//l Mn///op/q/rst/"),
|
|
"/abc/Def ijk/l Mn/op/q/rst")
|
|
self.assertEqual(self.cls.normalizeStringPath("abc/def"), "/abc/def")
|
|
self.assertEqual(self.cls.normalizeStringPath("/abc/def"), "/abc/def")
|
|
self.assertEqual(self.cls.normalizeStringPath("//abc/def"),
|
|
"/abc/def")
|
|
self.assertEqual(self.cls.normalizeStringPath("///abc/def"),
|
|
"/abc/def")
|
|
self.assertEqual(self.cls.normalizeStringPath("/abc//def"),
|
|
"/abc/def")
|
|
|
|
# Unless the implementation of VirtualPath.__init__() has changed
|
|
# meanwhile, test_creation() must be essentially the same as
|
|
# test_normalizeStringPath().
|
|
def test_creation(self):
|
|
p = self.cls("/")
|
|
self.assertEqual(str(p), "/")
|
|
|
|
p = self.cls("")
|
|
self.assertEqual(str(p), "/")
|
|
|
|
p = self.cls("/abc/Def ijk//l Mn///op/q/rst/")
|
|
self.assertEqual(str(p), "/abc/Def ijk/l Mn/op/q/rst")
|
|
|
|
p = self.cls("abc/def")
|
|
self.assertEqual(str(p), "/abc/def")
|
|
|
|
p = self.cls("/abc/def")
|
|
self.assertEqual(str(p), "/abc/def")
|
|
|
|
p = self.cls("//abc/def")
|
|
self.assertEqual(str(p), "/abc/def")
|
|
|
|
p = self.cls("///abc/def")
|
|
self.assertEqual(str(p), "/abc/def")
|
|
|
|
p = self.cls("/abc//def")
|
|
self.assertEqual(str(p), "/abc/def")
|
|
|
|
def test_samePath(self):
|
|
self.assertTrue(self.cls("").samePath(self.cls("")))
|
|
self.assertTrue(self.cls("").samePath(self.cls("/")))
|
|
self.assertTrue(self.cls("/").samePath(self.cls("")))
|
|
self.assertTrue(self.cls("/").samePath(self.cls("/")))
|
|
|
|
self.assertTrue(
|
|
self.cls("/abc/def").samePath(self.cls("/abc/def")))
|
|
self.assertTrue(
|
|
self.cls("/abc//def").samePath(self.cls("/abc/def")))
|
|
self.assertTrue(
|
|
self.cls("/abc/def/").samePath(self.cls("/abc/def")))
|
|
|
|
def test_str(self):
|
|
self.assertEqual(str(self.cls("/abc//def")), "/abc/def")
|
|
|
|
def test_comparisons(self):
|
|
self.assertEqual(self.cls("/abc/def"), self.cls("/abc/def"))
|
|
self.assertEqual(self.cls("/abc//def"), self.cls("/abc/def"))
|
|
self.assertEqual(self.cls("/abc/def/"), self.cls("/abc/def"))
|
|
|
|
self.assertNotEqual(self.cls("/abc/dEf"), self.cls("/abc/def"))
|
|
self.assertNotEqual(self.cls("/abc/def "), self.cls("/abc/def"))
|
|
|
|
self.assertLessEqual(self.cls("/foo/bar"), self.cls("/foo/bar"))
|
|
self.assertLessEqual(self.cls("/foo/bar"), self.cls("/foo/bbr"))
|
|
self.assertLess(self.cls("/foo/bar"), self.cls("/foo/bbr"))
|
|
|
|
self.assertGreaterEqual(self.cls("/foo/bar"), self.cls("/foo/bar"))
|
|
self.assertGreaterEqual(self.cls("/foo/bbr"), self.cls("/foo/bar"))
|
|
self.assertGreater(self.cls("/foo/bbr"), self.cls("/foo/bar"))
|
|
|
|
def test_truedivOperators(self):
|
|
"""
|
|
Test operators used to add paths components to a VirtualPath instance."""
|
|
p = self.cls("/foo/bar/baz/quux/zoot")
|
|
self.assertEqual(p, self.cls("/") / "foo" / "bar" / "baz/quux/zoot")
|
|
self.assertEqual(p, self.cls("/foo") / "bar" / "baz/quux/zoot")
|
|
self.assertEqual(p, self.cls("/foo/bar") / "baz/quux/zoot")
|
|
|
|
def test_joinpath(self):
|
|
p = self.cls("/foo/bar/baz/quux/zoot")
|
|
self.assertEqual(
|
|
p,
|
|
self.cls("/foo").joinpath("bar", "baz", "quux/zoot"))
|
|
|
|
def test_nameAttribute(self):
|
|
self.assertEqual(self.cls("/").name, "")
|
|
|
|
p = self.cls("/foo/bar/baz/quux/zoot")
|
|
self.assertEqual(p.name, "zoot")
|
|
|
|
def test_partsAttribute(self):
|
|
self.assertEqual(self.cls("/").parts, ("/",))
|
|
|
|
p = self.cls("/foo/bar/baz/quux/zoot")
|
|
self.assertEqual(p.parts, ("/", "foo", "bar", "baz", "quux", "zoot"))
|
|
|
|
def test_parentsAttribute(self):
|
|
def pathify(*args):
|
|
return tuple( (self.cls(s) for s in args) )
|
|
|
|
p = self.cls("/")
|
|
self.assertEqual(tuple(p.parents), pathify()) # empty tuple
|
|
|
|
p = self.cls("/foo")
|
|
self.assertEqual(tuple(p.parents), pathify("/"))
|
|
|
|
p = self.cls("/foo/bar")
|
|
self.assertEqual(tuple(p.parents), pathify("/foo", "/"))
|
|
|
|
p = self.cls("/foo/bar/baz")
|
|
self.assertEqual(tuple(p.parents), pathify("/foo/bar", "/foo", "/"))
|
|
|
|
def test_parentAttribute(self):
|
|
def pathify(s):
|
|
return self.cls(s)
|
|
|
|
p = self.cls("/")
|
|
self.assertEqual(p.parent, pathify("/"))
|
|
|
|
p = self.cls("/foo")
|
|
self.assertEqual(p.parent, pathify("/"))
|
|
|
|
p = self.cls("/foo/bar")
|
|
self.assertEqual(p.parent, pathify("/foo"))
|
|
|
|
p = self.cls("/foo/bar/baz")
|
|
self.assertEqual(p.parent, pathify("/foo/bar"))
|
|
|
|
def test_suffixAttribute(self):
|
|
p = self.cls("/")
|
|
self.assertEqual(p.suffix, '')
|
|
|
|
p = self.cls("/foo/bar/baz.py")
|
|
self.assertEqual(p.suffix, '.py')
|
|
|
|
p = self.cls("/foo/bar/baz.py.bla")
|
|
self.assertEqual(p.suffix, '.bla')
|
|
|
|
p = self.cls("/foo/bar/baz")
|
|
self.assertEqual(p.suffix, '')
|
|
|
|
def test_suffixesAttribute(self):
|
|
p = self.cls("/")
|
|
self.assertEqual(p.suffixes, [])
|
|
|
|
p = self.cls("/foo/bar/baz.py")
|
|
self.assertEqual(p.suffixes, ['.py'])
|
|
|
|
p = self.cls("/foo/bar/baz.py.bla")
|
|
self.assertEqual(p.suffixes, ['.py', '.bla'])
|
|
|
|
p = self.cls("/foo/bar/baz")
|
|
self.assertEqual(p.suffixes, [])
|
|
|
|
def test_asRelative(self):
|
|
self.assertEqual(self.cls("/").asRelative(), "")
|
|
self.assertEqual(self.cls("/foo/bar/baz/quux/zoot").asRelative(),
|
|
"foo/bar/baz/quux/zoot")
|
|
|
|
|
|
class TestVirtualPath(unittest.TestCase, VirtualPathCommonTests):
|
|
"""Tests for the VirtualPath class.
|
|
|
|
These are the tests using the common infrastructure from
|
|
VirtualPathCommonTests.
|
|
|
|
"""
|
|
|
|
cls = VirtualPath
|
|
|
|
class TestVirtualPathSpecific(unittest.TestCase):
|
|
"""Tests specific to the VirtualPath class."""
|
|
|
|
def test_isHashableType(self):
|
|
p = VirtualPath("/foo")
|
|
self.assertTrue(isinstance(p, collections.Hashable))
|
|
|
|
def test_insideSet(self):
|
|
l1 = [ VirtualPath("/foo/bar"),
|
|
VirtualPath("/foo/baz") ]
|
|
l2 = l1 + [ VirtualPath("/foo/bar") ] # l2 has a duplicate element
|
|
|
|
# Sets allow one to ignore duplicate elements when comparing
|
|
self.assertEqual(set(l1), set(l2))
|
|
self.assertEqual(frozenset(l1), frozenset(l2))
|
|
|
|
|
|
class TestMutableVirtualPath(unittest.TestCase, VirtualPathCommonTests):
|
|
"""Tests for the MutableVirtualPath class.
|
|
|
|
These are the tests using the common infrastructure from
|
|
VirtualPathCommonTests.
|
|
|
|
"""
|
|
|
|
cls = MutableVirtualPath
|
|
|
|
class TestMutableVirtualPathSpecific(unittest.TestCase):
|
|
"""Tests specific to the MutableVirtualPath class."""
|
|
|
|
def test_mixedComparisons(self):
|
|
self.assertTrue(
|
|
VirtualPath("/abc/def").samePath(MutableVirtualPath("/abc/def")))
|
|
self.assertTrue(
|
|
VirtualPath("/abc//def").samePath(MutableVirtualPath("/abc/def")))
|
|
self.assertTrue(
|
|
VirtualPath("/abc/def/").samePath(MutableVirtualPath("/abc/def")))
|
|
|
|
self.assertTrue(
|
|
MutableVirtualPath("/abc/def").samePath(VirtualPath("/abc/def")))
|
|
self.assertTrue(
|
|
MutableVirtualPath("/abc//def").samePath(VirtualPath("/abc/def")))
|
|
self.assertTrue(
|
|
MutableVirtualPath("/abc/def/").samePath(VirtualPath("/abc/def")))
|
|
|
|
def test_inPlacePathConcatenation(self):
|
|
p = VirtualPath("/foo/bar/baz/quux/zoot")
|
|
|
|
q = MutableVirtualPath("/foo")
|
|
q /= "bar"
|
|
q /= "baz/quux/zoot"
|
|
|
|
self.assertTrue(p.samePath(q))
|
|
|
|
def test_isNotHashableType(self):
|
|
p = MutableVirtualPath("/foo")
|
|
self.assertFalse(isinstance(p, collections.Hashable))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|