1"""Sanity test for symlinks in the bin directory.""" 2from __future__ import (absolute_import, division, print_function) 3__metaclass__ = type 4 5import os 6 7from .. import types as t 8 9from ..sanity import ( 10 SanityVersionNeutral, 11 SanityMessage, 12 SanityFailure, 13 SanitySuccess, 14) 15 16from ..config import ( 17 SanityConfig, 18) 19 20from ..data import ( 21 data_context, 22) 23 24from ..payload import ( 25 ANSIBLE_BIN_SYMLINK_MAP, 26 __file__ as symlink_map_full_path, 27) 28 29from ..util import ( 30 ANSIBLE_BIN_PATH, 31 ANSIBLE_TEST_DATA_ROOT, 32) 33 34 35class BinSymlinksTest(SanityVersionNeutral): 36 """Sanity test for symlinks in the bin directory.""" 37 ansible_only = True 38 39 @property 40 def can_ignore(self): # type: () -> bool 41 """True if the test supports ignore entries.""" 42 return False 43 44 @property 45 def no_targets(self): # type: () -> bool 46 """True if the test does not use test targets. Mutually exclusive with all_targets.""" 47 return True 48 49 # noinspection PyUnusedLocal 50 def test(self, args, targets): # pylint: disable=locally-disabled, unused-argument 51 """ 52 :type args: SanityConfig 53 :type targets: SanityTargets 54 :rtype: TestResult 55 """ 56 bin_root = ANSIBLE_BIN_PATH 57 bin_names = os.listdir(bin_root) 58 bin_paths = sorted(os.path.join(bin_root, path) for path in bin_names) 59 60 injector_root = os.path.join(ANSIBLE_TEST_DATA_ROOT, 'injector') 61 injector_names = os.listdir(injector_root) 62 63 errors = [] # type: t.List[t.Tuple[str, str]] 64 65 symlink_map_path = os.path.relpath(symlink_map_full_path, data_context().content.root) 66 67 for bin_path in bin_paths: 68 if not os.path.islink(bin_path): 69 errors.append((bin_path, 'not a symbolic link')) 70 continue 71 72 dest = os.readlink(bin_path) 73 74 if not os.path.exists(bin_path): 75 errors.append((bin_path, 'points to non-existent path "%s"' % dest)) 76 continue 77 78 if not os.path.isfile(bin_path): 79 errors.append((bin_path, 'points to non-file "%s"' % dest)) 80 continue 81 82 map_dest = ANSIBLE_BIN_SYMLINK_MAP.get(os.path.basename(bin_path)) 83 84 if not map_dest: 85 errors.append((bin_path, 'missing from ANSIBLE_BIN_SYMLINK_MAP in file "%s"' % symlink_map_path)) 86 continue 87 88 if dest != map_dest: 89 errors.append((bin_path, 'points to "%s" instead of "%s" from ANSIBLE_BIN_SYMLINK_MAP in file "%s"' % (dest, map_dest, symlink_map_path))) 90 continue 91 92 if not os.access(bin_path, os.X_OK): 93 errors.append((bin_path, 'points to non-executable file "%s"' % dest)) 94 continue 95 96 for bin_name, dest in ANSIBLE_BIN_SYMLINK_MAP.items(): 97 if bin_name not in bin_names: 98 bin_path = os.path.join(bin_root, bin_name) 99 errors.append((bin_path, 'missing symlink to "%s" defined in ANSIBLE_BIN_SYMLINK_MAP in file "%s"' % (dest, symlink_map_path))) 100 101 if bin_name not in injector_names: 102 injector_path = os.path.join(injector_root, bin_name) 103 errors.append((injector_path, 'missing symlink to "python.py"')) 104 105 messages = [SanityMessage(message=message, path=os.path.relpath(path, data_context().content.root), confidence=100) for path, message in errors] 106 107 if errors: 108 return SanityFailure(self.name, messages=messages) 109 110 return SanitySuccess(self.name) 111