blob: 996ae09195b4f15dbcdd4f5a76f875f9908a4916 [file]
#!/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()