summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichał Górny <mgorny@gentoo.org>2017-10-25 11:00:16 +0200
committerMichał Górny <mgorny@gentoo.org>2017-10-25 11:17:58 +0200
commitf1eae61437faf74ae1b970a2dc323ec33f210031 (patch)
treee821ddf4c89ffbd67b8c9faaadb1b09695204e93
parentd5e3e0d4dd0d2cdcc3d6b51efe70d62072d85eca (diff)
downloadgemato-f1eae61437faf74ae1b970a2dc323ec33f210031.tar.gz
Support finding top-level Manifest
-rw-r--r--gemato/find_top_level.py66
-rw-r--r--tests/test_find_top_level.py197
2 files changed, 263 insertions, 0 deletions
diff --git a/gemato/find_top_level.py b/gemato/find_top_level.py
new file mode 100644
index 0000000..41c5c66
--- /dev/null
+++ b/gemato/find_top_level.py
@@ -0,0 +1,66 @@
+# gemato: Top-level Manifest finding routine
+# vim:fileencoding=utf-8
+# (c) 2017 Michał Górny
+# Licensed under the terms of 2-clause BSD license
+
+import errno
+import io
+import os
+import os.path
+
+import gemato.manifest
+
+
+def find_top_level_manifest(path='.'):
+ """
+ Find top-level Manifest file that covers @path (defaults
+ to the current directory). Returns the path to the Manifest
+ or None.
+ """
+
+ cur_path = path
+ last_found = None
+ original_dev = None
+ m = gemato.manifest.ManifestFile()
+
+ root_st = os.stat('/')
+
+ while True:
+ st = os.stat(cur_path)
+
+ # verify that we are not crossing device boundaries
+ if original_dev is None:
+ original_dev = st.st_dev
+ elif original_dev != st.st_dev:
+ break
+
+ m_path = os.path.join(cur_path, 'Manifest')
+ try:
+ with io.open(m_path, 'r', encoding='utf8') as f:
+ fst = os.fstat(f.fileno())
+ if fst.st_dev != original_dev:
+ break
+
+ m.load(f)
+ except IOError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ else:
+ # check if the initial path is ignored
+ relpath = os.path.relpath(path, cur_path)
+ if relpath == '.':
+ relpath = ''
+ fe = m.find_path_entry(relpath)
+ if isinstance(fe, gemato.manifest.ManifestEntryIGNORE):
+ break
+
+ last_found = m_path
+
+ # check if we reached root directory
+ if st.st_dev == root_st.st_dev and st.st_ino == root_st.st_ino:
+ break
+
+ # try the parent directory
+ cur_path = os.path.join(cur_path, '..')
+
+ return last_found
diff --git a/tests/test_find_top_level.py b/tests/test_find_top_level.py
new file mode 100644
index 0000000..5cb349d
--- /dev/null
+++ b/tests/test_find_top_level.py
@@ -0,0 +1,197 @@
+# gemato: Top-level Manifest finding tests
+# vim:fileencoding=utf-8
+# (c) 2017 Michał Górny
+# Licensed under the terms of 2-clause BSD license
+
+import os
+import os.path
+import unittest
+
+import gemato.find_top_level
+
+from tests.testutil import TempDirTestCase
+
+
+class TestCurrentDirectory(TempDirTestCase):
+ """
+ Test for finding top-level Manifest in a plain tree.
+ """
+
+ DIRS = ['suba', 'subb', 'subc', 'subc/sub']
+ FILES = {
+ 'Manifest': u'',
+ 'subb/Manifest': u'',
+ 'subc/sub/Manifest': u'',
+ }
+
+ def test_find_top_level_manifest(self):
+ self.assertEqual(
+ os.path.relpath(
+ gemato.find_top_level.find_top_level_manifest(self.dir),
+ self.dir),
+ 'Manifest')
+
+ def test_find_top_level_manifest_from_empty_subdir(self):
+ self.assertEqual(
+ os.path.relpath(
+ gemato.find_top_level.find_top_level_manifest(
+ os.path.join(self.dir, 'suba')),
+ self.dir),
+ 'Manifest')
+
+ def test_find_top_level_manifest_from_manifest_subdir(self):
+ self.assertEqual(
+ os.path.relpath(
+ gemato.find_top_level.find_top_level_manifest(
+ os.path.join(self.dir, 'subb')),
+ self.dir),
+ 'Manifest')
+
+ def test_find_top_level_manifest_from_deep_manifest_subdir(self):
+ self.assertEqual(
+ os.path.relpath(
+ gemato.find_top_level.find_top_level_manifest(
+ os.path.join(self.dir, 'subc', 'sub')),
+ self.dir),
+ 'Manifest')
+
+
+class TestUnreadableManifest(TempDirTestCase):
+ """
+ Test whether the function fails correctly when it can not read
+ a Manifest file.
+ """
+
+ FILES = {
+ 'Manifest': u'',
+ }
+
+ def setUp(self):
+ super(TestUnreadableManifest, self).setUp()
+ os.chmod(os.path.join(self.dir, 'Manifest'), 0)
+
+ def test_find_top_level_manifest(self):
+ self.assertRaises(IOError,
+ gemato.find_top_level.find_top_level_manifest, self.dir)
+
+
+class TestIgnoredSubdir(TempDirTestCase):
+ """
+ Test for ignoring irrelevant Manifest.
+ """
+
+ DIRS = ['sub', 'sub/sub', 'subb', 'subempty']
+ FILES = {
+ 'Manifest': u'''
+IGNORE sub
+IGNORE subempty
+''',
+ 'sub/Manifest': u'',
+ }
+
+ def test_find_top_level_manifest(self):
+ self.assertEqual(
+ os.path.relpath(
+ gemato.find_top_level.find_top_level_manifest(self.dir),
+ self.dir),
+ 'Manifest')
+
+ def test_find_top_level_manifest_from_ignored_subdir(self):
+ self.assertEqual(
+ os.path.relpath(
+ gemato.find_top_level.find_top_level_manifest(
+ os.path.join(self.dir, 'sub')),
+ self.dir),
+ 'sub/Manifest')
+
+ def test_find_top_level_manifest_from_sub_subdir(self):
+ self.assertEqual(
+ os.path.relpath(
+ gemato.find_top_level.find_top_level_manifest(
+ os.path.join(self.dir, 'sub/sub')),
+ self.dir),
+ 'sub/Manifest')
+
+ def test_find_top_level_manifest_from_non_ignored_subdir(self):
+ self.assertEqual(
+ os.path.relpath(
+ gemato.find_top_level.find_top_level_manifest(
+ os.path.join(self.dir, 'subb')),
+ self.dir),
+ 'Manifest')
+
+ def test_find_top_level_manifest_from_ignored_empty_subdir(self):
+ self.assertIsNone(
+ gemato.find_top_level.find_top_level_manifest(
+ os.path.join(self.dir, 'subempty')))
+
+
+class TestEmptyTree(TempDirTestCase):
+ """
+ Test for finding top-level Manifest in a tree without a Manifest
+ """
+
+ def test_find_top_level_manifest(self):
+ self.assertIsNone(
+ gemato.find_top_level.find_top_level_manifest(self.dir))
+
+
+class TestRootDirectory(unittest.TestCase):
+ """
+ Test behavior when run on the system root directory.
+ """
+
+ def test_find_top_level_manifest(self):
+ if os.path.exists('/Manifest'):
+ raise unittest.SkipTest('/Manifest is present')
+ self.assertIsNone(
+ gemato.find_top_level.find_top_level_manifest('/'))
+
+
+class TestCrossDevice(TempDirTestCase):
+ """
+ Test behavior when attempting to cross device boundary.
+ """
+
+ FILES = {
+ 'Manifest': u'',
+ }
+
+ def setUp(self):
+ if not os.path.exists('/proc'):
+ raise unittest.SkipTest('/proc does not exist')
+ super(TestCrossDevice, self).setUp()
+ os.symlink('/proc', os.path.join(self.dir, 'test'))
+
+ def tearDown(self):
+ os.unlink(os.path.join(self.dir, 'test'))
+ super(TestCrossDevice, self).tearDown()
+
+ def test_find_top_level_manifest(self):
+ self.assertIsNone(
+ gemato.find_top_level.find_top_level_manifest(
+ os.path.join(self.dir, 'test')))
+
+
+class TestCrossDeviceManifest(TempDirTestCase):
+ """
+ Test behavior when attempting to use a Manifest from other device
+ (symlinked).
+ """
+
+ DIRS = ['sub']
+
+ def setUp(self):
+ if not os.path.exists('/proc/version'):
+ raise unittest.SkipTest('/proc/version does not exist')
+ super(TestCrossDeviceManifest, self).setUp()
+ os.symlink('/proc/version', os.path.join(self.dir, 'Manifest'))
+
+ def tearDown(self):
+ os.unlink(os.path.join(self.dir, 'Manifest'))
+ super(TestCrossDeviceManifest, self).tearDown()
+
+ def test_find_top_level_manifest(self):
+ self.assertIsNone(
+ gemato.find_top_level.find_top_level_manifest(
+ os.path.join(self.dir, 'sub')))