aboutsummaryrefslogtreecommitdiff
path: root/tests/install/test.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/install/test.py')
-rw-r--r--tests/install/test.py186
1 files changed, 186 insertions, 0 deletions
diff --git a/tests/install/test.py b/tests/install/test.py
new file mode 100644
index 0000000..996ae09
--- /dev/null
+++ b/tests/install/test.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import itertools
+import os
+import unittest
+import stat
+import subprocess
+
+from rules_python.python.runfiles import runfiles
+from pkg.private import manifest
+
+
+class PkgInstallTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.runfiles = runfiles.Create()
+ # Somewhat of an implementation detail, but it works. I think.
+ manifest_file = cls.runfiles.Rlocation("rules_pkg/tests/install/test_installer_install_script-install-manifest.json")
+
+ with open(manifest_file, 'r') as fh:
+ manifest_entries = manifest.read_entries_from(fh)
+ cls.manifest_data = {}
+
+ for entry in manifest_entries:
+ cls.manifest_data[entry.dest] = entry
+ cls.installdir = os.path.join(os.getenv("TEST_TMPDIR"), "installdir")
+ env = {}
+ env.update(cls.runfiles.EnvVars())
+ subprocess.check_call([
+ cls.runfiles.Rlocation("rules_pkg/tests/install/test_installer"),
+ "--destdir", cls.installdir,
+ "--verbose",
+ ],
+ env=env)
+
+ def entity_type_at_path(self, path):
+ if os.path.islink(path):
+ return manifest.ENTRY_IS_LINK
+ elif os.path.isfile(path):
+ return manifest.ENTRY_IS_FILE
+ elif os.path.isdir(path):
+ return manifest.ENTRY_IS_DIR
+ else:
+ # We can't infer what TreeArtifacts are by looking at them -- the
+ # build system is not aware of their contents.
+ raise ValueError("Entity {} is not a link, file, or directory")
+
+ def assertEntryTypeMatches(self, entry, actual_path):
+ actual_entry_type = self.entity_type_at_path(actual_path)
+ self.assertEqual(actual_entry_type, entry.type,
+ "Entity {} should be a {}, but was actually {}".format(
+ entry.dest,
+ manifest.entry_type_to_string(entry.type),
+ manifest.entry_type_to_string(actual_entry_type),
+ ))
+
+ def assertEntryModeMatches(self, entry, actual_path):
+ # TODO: permissions in windows are... tricky. Don't bother
+ # testing for them if we're in it for the time being
+ if os.name == 'nt':
+ return
+
+ actual_mode = stat.S_IMODE(os.stat(actual_path).st_mode)
+ expected_mode = int(entry.mode, 8)
+ self.assertEqual(actual_mode, expected_mode,
+ "Entry {} has mode {:04o}, expected {:04o}".format(
+ entry.dest, actual_mode, expected_mode,
+ ))
+
+ def test_manifest_matches(self):
+ unowned_dirs = set()
+ owned_dirs = set()
+
+ # Figure out what directories we are supposed to own, and which ones we
+ # aren't.
+ #
+ # Unowned directories are created implicitly by requesting other
+ # elements be created or installed.
+ #
+ # Owned directories are created explicitly with the pkg_mkdirs rule.
+ for dest, data in self.manifest_data.items():
+ if data.type == manifest.ENTRY_IS_DIR:
+ owned_dirs.add(dest)
+
+ # TODO(nacl): The initial stage of the accumulation returns an empty string,
+ # which end up in the set representing the root of the manifest.
+ # This may not be the best thing.
+ unowned_dirs.update([p for p in itertools.accumulate(os.path.dirname(dest).split('/'),
+ func=lambda accum, new: accum + '/' + new)])
+
+ # In the above loop, unowned_dirs contains all possible directories that
+ # are in the manifest. Prune them here.
+ unowned_dirs -= owned_dirs
+
+ # TODO: check for ownership (user, group)
+ found_entries = {dest: False for dest in self.manifest_data.keys()}
+ for root, dirs, files in os.walk(self.installdir):
+ rel_root_path = os.path.relpath(root, self.installdir)
+
+ # The rest of this uses string comparison. To reduce potential
+ # confusion, ensure that the "." doesn't show up elsewhere.
+ #
+ # TODO(nacl) consider using pathlib here, which will reduce the
+ # need for path cleverness.
+ if rel_root_path == '.':
+ rel_root_path = ''
+
+ # TODO(nacl): check for treeartifacts here. If so, prune `dirs`,
+ # and set the rest aside for future processing.
+
+ # Directory ownership tests
+ if len(files) == 0 and len(dirs) == 0:
+ # Empty directories must be explicitly requested by something
+ if rel_root_path not in self.manifest_data:
+ self.fail("Directory {} not in manifest".format(rel_root_path))
+
+ entry = self.manifest_data[rel_root_path]
+ self.assertEntryTypeMatches(entry, root)
+ self.assertEntryModeMatches(entry, root)
+
+ found_entries[rel_root_path] = True
+ else:
+ # There's something in here. Depending on how it was set up, it
+ # could either be owned or unowned.
+ if rel_root_path in self.manifest_data:
+ entry = self.manifest_data[rel_root_path]
+ self.assertEntryTypeMatches(entry, root)
+ self.assertEntryModeMatches(entry, root)
+
+ found_entries[rel_root_path] = True
+ else:
+ # If any unowned directories are here, they must be the
+ # prefix of some entity in the manifest.
+ self.assertIn(rel_root_path, unowned_dirs)
+
+ for f in files:
+ # The path on the filesystem in which the file actually exists.
+
+ # TODO(#382): This part of the test assumes that the path
+ # separator is '/', which is not the case in Windows. However,
+ # paths emitted in the JSON manifests may also be using
+ # '/'-separated paths.
+ #
+ # Confirm the degree to which this is a problem, and remedy as
+ # needed. It maybe worth setting the keys in the manifest_data
+ # dictionary to pathlib.Path or otherwise converting them to
+ # native paths.
+ fpath = os.path.normpath("/".join([root, f]))
+ # The path inside the manifest (relative to the install
+ # destdir).
+ rel_fpath = os.path.normpath("/".join([rel_root_path, f]))
+ if rel_fpath not in self.manifest_data:
+ self.fail("Entity {} not in manifest".format(rel_fpath))
+
+ entry = self.manifest_data[rel_fpath]
+ self.assertEntryTypeMatches(entry, fpath)
+ self.assertEntryModeMatches(entry, fpath)
+
+ found_entries[rel_fpath] = True
+
+ # TODO(nacl): check for TreeArtifacts
+
+ num_missing = 0
+ for dest, present in found_entries.items():
+ if present is False:
+ print("Entity {} is missing from the tree".format(dest))
+ num_missing += 1
+ self.assertEqual(num_missing, 0)
+
+
+if __name__ == "__main__":
+ unittest.main()